Прежде всего, я запускаю это на ноутбуке 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), оно будет правильно помечено как завершенное. Такое поведение смущает меня...
Как я могу продолжать проверять статус будущего внутри формулировки цикла?
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()
Кроме того, с таким будущим ваш исходный код будет работать без изменений.