Вопрос с собеседования · Python

Как в asyncio работают отмена задач и таймауты?

MiddleКонкурентностьasyncioPython >=3.11
Ещё спрашивают так:
  • Что такое 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

Разбор темы

Связанные вопросы

Партнёр платформы

Разбор собеседования с sotto

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

Разбор собеседования