Что такое блокирующие функции в асинхронном программировании
Что проверяют
Понимаете ли вы, что цикл событий asyncio живёт в одном потоке, и что синхронный вызов внутри корутины останавливает не только её, а весь сервис. Это ключ к тому, почему асинхронный код на практике иногда оказывается медленнее обычного.
Как отвечать
Блокирующая функция удерживает поток, пока ждёт результата: обращение по сети через requests, обычное открытие и чтение файла, time.sleep, драйвер базы на синхронной библиотеке, а также любой долгий счёт.
В asyncio управление возвращается планировщику только на await. Пока корутина выполняет синхронный вызов, ни один другой запрос не обрабатывается, таймеры не срабатывают, отмена не доходит. Секунда ожидания ответа это секунда простоя всех соединений процесса.
Порядок действий: взять асинхронный аналог библиотеки, а если его нет, увести вызов в отдельный поток.
rows = await asyncio.to_thread(legacy_query, sql)Для вычислений потоки не спасают из-за глобальной блокировки интерпретатора, там нужен пул процессов. Найти виновника помогает отладочный режим цикла: он предупреждает о вызове, который выполнялся подозрительно долго.
Где спотыкаются
Считают, что asyncio даёт параллельное выполнение, и ждут ускорения расчётов. Он даёт конкурентность на ожидании ввода и вывода, а счёт всё так же занимает единственный поток.
Оборачивают синхронную функцию в async def и считают задачу решённой. Ключевое слово не делает вызов неблокирующим, оно лишь разрешает внутри await; тело выполняется ровно так же и так же держит цикл.
Оставляют в корутинах time.sleep вместо asyncio.sleep и обычную работу с файлами. Такие места не видны в профиле по процессору и проявляются плавающими таймаутами у соседних запросов под нагрузкой.