Статус будущего внутри цикла всегда в ожидании - PullRequest
1 голос
/ 14 апреля 2019

Прежде всего, я запускаю это на ноутбуке Jupyter, и это может быть причиной моего замешательства.

У меня есть функция foo, которая выполняет некоторые операции ввода-вывода:

def foo():
    # Dom some stuff
    time.sleep(1.)
    return 'Finished'

Я хочу иметь возможность запускать эту функцию в фоновом режиме, поэтому я решил использовать run_in_executor:

future = asyncio.get_event_loop().run_in_executor(None, foo)

Теперь операция не блокируется, и все в порядке, но если это будущее возвращает ошибку, я хочу перехватить ее и остановить основную программу, поэтому я решил написать цикл, который продолжает проверять наличиестатус этого будущего:

while True:
    time.sleep(0.1)
    if future.done():
    # Do some stuff, like raising any caught exception

Проблема в том, что будущий статус никогда не изменяется, внутри цикла он всегда в ожидании .

Если вместо проверки в цикле я вручную проверю состояние будущего (в блокноте jupyter), оно будет правильно помечено как завершенное.Это поведение сбивает меня с толку ...

Как я могу продолжать проверять статус будущего внутри формулировки цикла?

1 Ответ

2 голосов
/ 14 апреля 2019

run_in_executor разработан для использования в asyncio , поэтому его возвращаемое значение - asyncio Future, которым манипулирует поток, выполняющий цикл событий asyncio. Поскольку ваш код вращается в цикле while, ничего не ожидая, он не дает возможности циклу событий вообще запускаться, эффективно блокируя цикл событий. Будущее остается "ожидающим", потому что обратный вызов, который должен был бы обновиться, должен вызываться циклом событий, который в настоящее время не работает - он просто находится в очереди.

Замена time.sleep(0.1) на await asyncio.sleep(0.1), вероятно, решит проблему. Но тогда вам вообще не нужен цикл while; поскольку будущее асинхронно ожидаемо, вы можете await напрямую:

await future
# Do some stuff, with the future done.

await приостанавливает текущую сопрограмму до тех пор, пока не завершится будущее, предоставляя другим задачам возможность выполнить в это время. Возвращает значение будущего или распространяет исключение.

Альтернатива - вообще не использовать asyncio, а использовать concurrent.futures напрямую. Таким образом вы получите ожидаемую семантику потоков (истинное «фоновое» выполнение) и Future, который работает соответственно.

# keep this in a global variable, so that the same executor
# is reused for multiple calls
executor = concurrent.futures.ThreadPoolExecutor()

# later, submit foo to be executed in the background
future = executor.submit(foo)

Это будущее concurrent.futures будущее , которое поддерживает ожидание такого результата:

result = future.result()

Кроме того, с таким будущим ваш оригинальный код будет работать без изменений.

...