Ответ
Нет, Event Loop не переключится на другую задачу. Он будет заблокирован до тех пор, пока CPU-bound операция не завершится.
Почему так происходит?
asyncio
использует модель кооперативной многозадачности. Это означает, что корутина должна явно передать управление обратно в Event Loop с помощью ключевого слова await
. CPU-bound задачи (например, сложные вычисления, обработка изображений) не содержат await
, поэтому они монополизируют поток выполнения и блокируют весь цикл событий.
Проблема
В этом примере other_task
никогда не получит управление, пока cpu_bound_task
не завершит свои долгие вычисления.
import asyncio
import time
async def cpu_bound_task():
print("CPU-bound задача началась...")
# Симуляция долгой блокирующей операции
sum(i*i for i in range(10**7))
print("CPU-bound задача завершена.")
async def other_task():
print("Другая задача выполняется в фоне...")
await asyncio.sleep(1)
async def main():
# other_task не сможет выполниться, пока cpu_bound_task работает
await asyncio.gather(cpu_bound_task(), other_task())
# asyncio.run(main()) # Этот код заблокирует Event Loop
Решение
Для выполнения блокирующих CPU-bound задач в асинхронном коде следует использовать loop.run_in_executor()
. Этот метод выносит выполнение функции в отдельный поток (или процесс), не блокируя Event Loop.
import asyncio
async def cpu_bound_task_fixed():
print("CPU-bound задача началась в executor...")
loop = asyncio.get_running_loop()
# Выполняем блокирующую функцию в пуле потоков по умолчанию
await loop.run_in_executor(
None,
lambda: sum(i*i for i in range(10**7))
)
print("CPU-bound задача завершена.")
async def main_fixed():
# Теперь задачи выполняются конкурентно
await asyncio.gather(cpu_bound_task_fixed(), other_task())
asyncio.run(main_fixed())
Таким образом, для CPU-bound операций необходимо использовать экзекуторы, чтобы сохранить отзывчивость асинхронного приложения.