Что произойдет, если в корутине asyncio использовать блокирующую операцию ввода-вывода, например, open().read()?

«Что произойдет, если в корутине asyncio использовать блокирующую операцию ввода-вывода, например, open().read()?» — вопрос из категории Асинхронность, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Использование синхронной блокирующей операции, такой как open().read(), внутри корутины полностью заблокирует цикл событий (event loop) asyncio.

Пока выполняется длительная блокирующая операция, event loop не может переключаться на другие задачи. В результате все остальные корутины останавливаются и ждут ее завершения. Это сводит на нет все преимущества асинхронности и может привести к "зависанию" всего приложения.

Неправильно (блокирует event loop):

import asyncio

async def blocking_task():
    print("Начинаю читать большой файл...")
    # Эта операция блокирует весь event loop
    with open('large_file.dat', 'rb') as f:
        _ = f.read()
    print("Файл прочитан.")

# Другие задачи не смогут выполниться, пока файл не будет прочитан

Правильные подходы:

  1. Использовать асинхронные библиотеки, например aiofiles. Они специально разработаны для интеграции с event loop.

    import aiofiles
    
    async def non_blocking_read():
        async with aiofiles.open('large_file.dat', mode='rb') as f:
            contents = await f.read()
        print("Файл прочитан асинхронно.")
  2. Вынести блокирующий код в отдельный поток с помощью loop.run_in_executor(). Это позволяет event loop продолжать работу, пока блокирующая операция выполняется в фоновом потоке.

    async def run_in_executor_read():
        loop = asyncio.get_running_loop()
        with open('large_file.dat', 'rb') as f:
            # Делегируем блокирующий вызов f.read() в пул потоков
            await loop.run_in_executor(None, f.read)
        print("Файл прочитан в отдельном потоке.")

Ключевой вывод: никогда не используйте длительные блокирующие вызовы (I/O, CPU-bound) напрямую в корутинах. Всегда используйте await с асинхронными функциями или делегируйте блокирующий код в executor.