Что будет, если внутри async-функции вызвать блокирующий код?
- Как вызвать синхронную библиотеку из asyncio?
- Зачем нужны asyncio.to_thread и run_in_executor?
Встанет весь event loop: блокирующий вызов (time.sleep, requests.get, синхронный драйвер БД, тяжёлое вычисление) не отдаёт управление, и все остальные задачи ждут. Решение — асинхронная библиотека (httpx, asyncpg, aiofiles) или вынос в поток: asyncio.to_thread / loop.run_in_executor.
- Для CPU-тяжёлого — ProcessPoolExecutor: потоки упрутся в GIL
- Найти блокировки: asyncio debug mode предупреждает о шагах дольше 100 мс
- Симптом — растут задержки всех запросов, хотя CPU почти не загружен
А ещё Во FastAPI обычный def-обработчик сам выполняется в пуле потоков, async def — в цикле
Подробно
Почему так. Цикл переключает задачи только в точках await. Синхронная функция не содержит await — пока она работает, цикл стоит. Сервер на asyncio с одним requests.get на 2 секунды в обработчике на эти 2 секунды перестаёт отвечать всем.
Варианты решения:
# 1. асинхронная библиотека — лучший вариант
async with httpx.AsyncClient() as client:
r = await client.get(url)
# 2. блокирующий ввод-вывод — в поток
data = await asyncio.to_thread(legacy_client.fetch, url)
# 3. вычисления — в процесс
loop = asyncio.get_running_loop()
with ProcessPoolExecutor() as pool:
result = await loop.run_in_executor(pool, heavy_compute, data)
to_thread использует пул потоков цикла по умолчанию (ограниченного размера) — массовые блокирующие вызовы в нём тоже выстроятся в очередь.
Как найти: asyncio.run(main(), debug=True) или PYTHONASYNCIODEBUG=1 логирует «Executing … took 0.5 seconds». В проде — метрика задержки цикла (lag) и профилировщик (py-spy).
Тонкость FastAPI: обработчик, объявленный через def, FastAPI сам запускает в пуле потоков, а async def — прямо в цикле. Блокирующий вызов внутри async def — частая ошибка.
Что хотят услышать
- Блокировка всего цикла и почему.
- to_thread, run_in_executor, асинхронные библиотеки.
- Процессы для вычислений.
- Как обнаружить.
Частые ошибки
- time.sleep вместо asyncio.sleep.
- Синхронный драйвер БД в async-обработчике.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✗Блокируется только текущая корутина, остальные продолжают работать в том же цикле
- ✗asyncio сам замечает блокирующие вызовы и автоматически выносит их в пул потоков
- ✗Для CPU-тяжёлых вычислений лучше всего asyncio.to_thread: поток в нём обходит GIL
- ✓Встанет весь event loop; нужна async-библиотека или asyncio.to_thread
Что спросят дальше
Разбор темы
- asyncio — корутины, задачи, отмена и блокирующий код
Как работают async и await, как запускать корутины одновременно, gather и TaskGroup, что делает блокирующий вызов в цикле событий, отмена и таймауты.
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.