Поведение asyncio.create_task и завершение loop

Поведение asyncio.create_task и завершение loop

@python_quiz

Разберем этот квиз

Короткий заголовок: Что произойдёт, если создать фоную задачу и не ждать её в asyncio.run

Условие (код)

python

Коротко: main создаёт задачу для bg и сразу печатает "done". Что будет напечатано при запуске этого скрипта и почему?

Что реально происходит

При запуске:

  • Выводится "done".
  • Затем loop завершает выполнение main, и asyncio.run инициирует отмену всех незавершённых задач, в том числе только что созданной bg.
  • Поскольку bg ожидает sleep(0.1) и вряд ли успеет завершиться до отмены, строка "bg" обычно не будет напечатана.

Итоговое наблюдаемое поведение: будет выведено только "done" — созданная фоновая задача отменяется при завершении asyncio.run, если её не дождались явно.

Почему так работает — подробное объяснение

  • asyncio.create_task ставит корутину в очередь выполнения и возвращает Task. Но это не await — main продолжает выполняться синхронно до следующего переключения контекста.
  • В данном main нет явного await для созданной задачи и нет дополнительной паузы, чтобы передать управление планировщику задач. Поэтому main быстро завершает выполнение после print('done').
  • asyncio.run запускает loop, выполняет задачу main и, когда main завершён, по спецификации отменяет все оставшиеся задачи (чтобы не оставить "висячие" задачи при завершении программы). Отмена происходит через отправку CancelledError задачам и ожидание их завершения очистки. Если bg не успела завершиться до этого, она получает отмену и, в большинстве случаев, не успевает выполнить print.

Замечание: теоретически задача могла успеть выполниться за короткий период между create_task и завершением main (например, если bg не ждёт, а делает синхронную работу), но в данном коде bg ждет 0.1 секунды, а main завершается почти мгновенно, поэтому bg не успеет.

Анализ вариантов ответа (ссылки на формулировки)

  • "Выведет \"done\" и завершится сразу, созданная задача будет отменена" — соответствует описанному поведению.
  • "Выведет \"done\", затем через ~0.1с выведет \"bg\" и завершится" — это случится только если main даст управление циклу достаточно рано (например, если main сам подождёт).
  • "Выведет и \"done\", и \"bg\", порядок неопределёнен" — неверно для данного кода, порядок в данном случае детерминирован: main выполняется и печатает "done" прежде чем bg сможет перейти к исполнению.
  • "Выбросит исключение из-за не await-нутой задачи" — тоже неверно: не-await задачи не приводят к исключению в момент создания; вместо этого они отменяются при завершении run.

Как заставить bg выполниться

Варианты, позволяющие увидеть "bg":

1) Явно дождаться задачи:

python

2) Дать цикл хотя бы немного времени на выполнение фоновой задачи:

python

3) Сразу yield управлением (часто используется await asyncio.sleep(0) для передачи контроля планировщику):

python

(В последнем случае bg может начать выполнение, но тут нет гарантии, что она успеет завершиться до завершения main — лучше ждать нужное время или await задачи.)

Наблюдение отмены внутри корутины

Чтобы увидеть, что задача действительно была отменена, можно обработать CancelledError внутри bg:

python

Запуск покажет "done" и затем — скорее всего — "bg: cancelled".

Выводы и рекомендации

  • asyncio.create_task запускает фоновые задачи, но их нужно явно ожидать, если вы хотите, чтобы они гарантированно завершились до окончания loop.
  • asyncio.run отменяет незавершённые задачи при завершении основной корутины, поэтому простое создание задачи без await обычно приведёт к её отмене.
  • Используйте await task, asyncio.gather или давайте loop время (await asyncio.sleep) — в зависимости от того, нужно ли действительно дождаться результата фоновой задачи.

Report Page