Как в asyncio работают отмена задач и таймауты?
- Что такое CancelledError и можно ли его перехватывать?
- Как ограничить время выполнения корутины?
task.cancel() бросает CancelledError внутрь корутины в точке ближайшего await. Таймаут — asyncio.timeout() (3.11) или wait_for: по истечении задача отменяется, наружу — TimeoutError. CancelledError нельзя глотать: очистить ресурсы и пробросить дальше.
- CancelledError наследует BaseException (3.8+) — except Exception его не ловит
- Очистка — в finally или async with
- asyncio.shield защищает внутреннюю операцию от отмены внешней
А ещё Синхронный код без await отменить нельзя — отмена сработает только на следующем await
Подробно
Механика. cancel() лишь помечает задачу; при следующем шаге в неё бросается CancelledError из того await, на котором она стоит. Корутина может выполнить очистку:
async def worker():
conn = await pool.acquire()
try:
await conn.execute(...)
finally:
await pool.release(conn) # выполнится и при отмене
Глотать нельзя. Если поймать CancelledError и не пробросить, вызывающий будет считать, что задача отменена, а она продолжит работу; TaskGroup и asyncio.timeout сломаются. Поймали для логирования — raise в конце.
Таймауты:
async with asyncio.timeout(2): # Python 3.11
await fetch()
# или
await asyncio.wait_for(fetch(), timeout=2)
При истечении внутренняя задача отменяется, наружу выходит TimeoutError.
shield. await asyncio.shield(save()) — внешняя отмена не прервёт сохранение, но вызывающий всё равно получит CancelledError.
Практика: таймаут на каждый внешний вызов; отмена распространяется сверху вниз — при отключении клиента фреймворк отменяет обработчик, и это должно корректно освобождать ресурсы.
Что хотят услышать
- Как доставляется отмена.
- Очистка ресурсов и запрет глотать CancelledError.
- asyncio.timeout и wait_for.
Частые ошибки
- except Exception: pass вокруг await — под Python 3.7 съедал и отмену.
- Таймаут без отмены внутренней операции (запрос продолжает висеть).
Проверьте себя
Несколько верных вариантов. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✗CancelledError нужно перехватить и не пробрасывать дальше, иначе задача упадёт
- ✓task.cancel() бросает CancelledError в ближайшем await
- ✗except Exception ловит CancelledError, начиная с Python 3.8
- ✓Таймаут — asyncio.timeout() или wait_for, наружу приходит TimeoutError
Разбор темы
- asyncio — корутины, задачи, отмена и блокирующий код
Как работают async и await, как запускать корутины одновременно, gather и TaskGroup, что делает блокирующий вызов в цикле событий, отмена и таймауты.
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.