Вопрос проверяет понимание типичных ловушек при работе с asyncio и асинхронными фреймворками, которые приводят к неожиданному падению производительности.
В асинхронном коде часто встречаются ошибки: использование блокирующих операций (например, time.sleep или requests) внутри async функций, забытый await при вызове корутин, из-за чего они не выполняются параллельно, а просто создаются. Также вредно запускать тяжёлые CPU-вычисления в event loop без вынесения в отдельный поток/процесс. Ещё одна типичная ошибка — слишком мелкое или наоборот слишком крупное разбиение задач: слишком много корутин создаёт накладные расходы, а слишком мало блокирует event loop. Все эти проблемы приводят к тому, что приложение теряет преимущество асинхронной модели и начинает вести себя как медленный синхронный код.
Определение.
Асинхронный код в Python — это код на основе async/await, который использует event loop для переключения между задачами во время ожидания I/O, не блокируя поток.
Принцип:
Пока одна корутина ждёт I/O, event loop может выполнять другую.
Если внутри async функции делать блокирующие операции, event loop «замирает».
awaitОдна из самых частых ошибок:
Python
async def handler():
do_work() # ОШИБКА: забыли await
Проблема:
Если do_work — корутина, без await она не запускается, а просто создаётся объект coroutine.
Логика не выполняется вовремя или вообще не выполняется.
Или код выполняется последовательно, а не параллельно, если мы собирались использовать asyncio.gather.
Правильный вариант:
Python
async def handler():
await do_work()
Или, если нужно параллельно:
Python
async def handler():
task1 = asyncio.create_task(do_work1())
task2 = asyncio.create_task(do_work2())
await asyncio.gather(task1, task2)
async функцийЧастая ошибка — использовать синхронные блокирующие вызовы:
time.sleep вместо asyncio.sleep.
requests.get вместо httpx.AsyncClient().get.
Тяжёлые вычисления на CPU в обычном коде.
Пример ошибки:
Python
import time
async def handler():
time.sleep(5) # блокирует event loop на 5 секунд
Правильно:
Python
import asyncio
async def handler():
await asyncio.sleep(5)
Для HTTP-запросов:
Неправильно: requests.get(...) в async коде.
Правильно: httpx.AsyncClient().get(...) или aiohttp.
Асинхронность не ускоряет CPU-вычисления. Если в async функции делать тяжёлые операции (парсинг огромного файла, сложный цикл):
Event loop не переключается на другие задачи.
Все остальные запросы «замирают».
Решение:
Вынести тяжёлый код в отдельный поток/процесс:
asyncio.to_thread.
ProcessPoolExecutor.
Python
import asyncio
def heavy_cpu():
# тяжёлые вычисления
...
async def handler():
result = await asyncio.to_thread(heavy_cpu)
return result
asyncio.gather и задачОшибки:
Не использовать gather, когда можно параллелить независимые операции.
Создавать слишком много задач (десятки тысяч одновременно), что приводит к накладным расходам.
Важно:
Параллелить только то, что реально может выполняться одновременно (I/O).
Ограничивать количество одновременных задач (semaphore, пул).
Иногда код формально асинхронный (async def), но внутри всё равно:
Синхронный ORM (без async).
Синхронные HTTP-вызовы.
Синхронный драйвер Redis/кэша.
В итоге:
Нет выигрыша от async, а код становится сложнее.
Лучше либо перейти на async-версии библиотек, либо оставить код синхронным.
Типичные ошибки в асинхронном коде — это забытый await, использование блокирующих операций в async функциях, тяжёлые CPU-вычисления в event loop и неправильная организация параллелизма (слишком много или слишком мало задач). Все они приводят к тому, что приложение перестаёт эффективно использовать асинхронную модель и работает медленнее, чем могло бы. Чтобы этого избежать, нужно осознанно использовать await, выбирать async-библиотеки и выносить тяжёлые операции из event loop.