TGViewer
About Python [ru] About Python [ru] @python_tesst · 6.45K subscribers
Post #2762 415
⁣asyncio зависает без ошибок? TaskGroup, timeout-декораторы и context vars для надежного трейсинга

Когда asyncio-задача “зависает”, стектрейс часто пуст или уводит в Event Loop. В production с многотысячными коннектами это тихая катастрофа: задача не падает, но и не завершается, ресурсы утекают. Разбираем три приёма, которые превращают отладку из гадания в детерминированный процесс.

TaskGroup и asyncio.timeout: границы времени жизни
С asyncio.TaskGroup (Python 3.11+) каждая задача имеет явный контекст. Комбинируя его с asyncio.timeout, получаем детектор зависаний:

async def safe_polling():
async with asyncio.TaskGroup() as tg:
async with asyncio.timeout(5.0):
task = tg.create_task(long_pipeline())


Плюс: при превышении лимита – TimeoutError с отменой корутины. Минус: нужно явно оборачивать каждую группу.

Timeout-декоратор: защита на уровне функции
Для всех внешних вызовов (API, базы, очереди) декоратор автоматически ставит таймаут:

import asyncio
from functools import wraps

def timeout(max_time: float):
def decorator(coro):
@wraps(coro)
async def wrapper(*args, **kwargs):
try:
return await asyncio.wait_for(
coro(*args, **kwargs), timeout=max_time)
except asyncio.TimeoutError:
log.warning(f"{coro.__name__} exceeded {max_time}s")
raise
return wrapper
return decorator

@timeout(3.0)
async def fetch_external_data(): ...


asyncio.wait_for корректно отменяет корутину, не оставляя её в состоянии “in progress”.

Context Vars для трейсинга: кто вызвал задачу
contextvars.ContextVar хранит идентификатор запроса или таски. При таймауте логгер выводит полную цепочку:

request_id = contextvars.ContextVar('request_id', default=None)

async def handler(call_id: str):
request_id.set(call_id)
async with asyncio.TaskGroup() as tg:
tg.create_task(process())


В логах видно request_id зависшей задачи — это ключ к поиску в трейсинге (OpenTelemetry, Jaeger).

Что ещё проверить

* Блокирующий синхронный код (requests.get вместо aiohttp)?
* Забытый await – asyncio пускает корутину без ошибки.
* Обилие таймаутов на разных уровнях: один для HTTP, другой для всей группы.

Вывод:
Замороженные asyncio-задачи отлавливаются только комбинацией явных границ времени (TaskGroup + timeout) и трейсинга исполнения (context vars), а не надеждой на “авось завершится”.
More from @python_tesst
  1. Sep 28, 2026Post #3191
  2. Sep 27, 2026OpenAI метит в подписку за 500 баксов Что там в описании тарифа? Пока что от ChatGPT Pro о…
  3. Sep 27, 2026Post #3189
  4. Sep 27, 2026Свежая обложка The Economist подъехала Журналисты: да мы вообще не сгущаем краски Те же жу…
  5. Sep 27, 2026Ночная годнота: Docker выкатила первые официальные скиллы для ИИ-агентов, которые ковыряют…
  6. Sep 26, 2026Claude Code больше не будет рубить всё на полуслове, если 5-часовой лимит прилетит прямо п…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →