Поведение asyncio.create_task и завершение loop
@python_quizРазберем этот квиз
Короткий заголовок: Что произойдёт, если создать фоную задачу и не ждать её в asyncio.run
Условие (код)

Коротко: 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) Явно дождаться задачи:

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

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

(В последнем случае bg может начать выполнение, но тут нет гарантии, что она успеет завершиться до завершения main — лучше ждать нужное время или await задачи.)
Наблюдение отмены внутри корутины
Чтобы увидеть, что задача действительно была отменена, можно обработать CancelledError внутри bg:

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