Ответ
Асинхронный event loop в Python работает в одном потоке и блокируется длительными, ресурсоемкими (CPU-bound) вычислениями. Чтобы этого избежать, такие задачи необходимо выносить в отдельный процесс.
Основной способ — использовать run_in_executor() с concurrent.futures.ProcessPoolExecutor.
Пример:
import asyncio
from concurrent.futures import ProcessPoolExecutor
import time
def cpu_bound_operation(n):
"""Имитация тяжелой вычислительной задачи."""
count = 0
for i in range(n):
count += i
return count
async def main():
loop = asyncio.get_running_loop()
# Создаем пул процессов для выполнения CPU-bound задач
with ProcessPoolExecutor() as pool:
# Выносим блокирующую функцию в другой процесс,
# не замораживая event loop.
result = await loop.run_in_executor(
pool, cpu_bound_operation, 10**7
)
print(f"Результат вычислений: {result}")
# Запускаем асинхронную программу
asyncio.run(main())
Ключевые моменты:
ProcessPoolExecutorзапускает задачу в отдельном процессе, используя все доступные ядра CPU и не блокируя основной процесс с event loop.loop.run_in_executor()"оборачивает" блокирующий вызов вawaitableобъект, позволяя event loop продолжать обрабатывать другие задачи, пока вычисления выполняются в фоне.- Для I/O-bound задач (ожидание сети, диска) этот подход избыточен. Достаточно стандартных
async/awaitи библиотек вродеaiohttp, так как event loop эффективно переключается между ними во время ожидания. - Альтернатива для потоков: Для блокирующих, но не ресурсоемких задач (например, вызов старой синхронной I/O-библиотеки), можно использовать
ThreadPoolExecutorили, начиная с Python 3.9, более удобныйasyncio.to_thread().