Как работает модуль `asyncio` в Python?

Ответ

Библиотека asyncio в Python предоставляет инфраструктуру для написания конкурентного кода с использованием корутин (coroutines) через синтаксис async/await. Она позволяет эффективно управлять множеством I/O-bound операций без использования потоков или процессов.

Почему используется: asyncio идеально подходит для задач, где много времени тратится на ожидание (например, сетевые запросы, чтение/запись файлов, запросы к базам данных), позволяя программе переключаться между задачами вместо блокировки.

Основные компоненты:

  • Event Loop (Цикл событий): Сердце asyncio, которое управляет выполнением задач, переключаясь между ними при обнаружении блокирующих операций (I/O, asyncio.sleep).
  • Coroutines (Корутины): Специальные функции, объявленные с async def, которые могут приостанавливать своё выполнение с помощью await и передавать управление обратно в Event Loop.
  • Tasks (Задачи): Обёртки вокруг корутин, которые планируются для выполнения в Event Loop. asyncio.create_task() превращает корутину в задачу.
  • Futures (Будущие объекты): Объекты, представляющие результат асинхронной операции, который будет доступен в будущем.

Пример:

import asyncio
import time

async def fetch_data(delay, data_id):
    print(f"[{time.time():.2f}] Начинаем загрузку данных {data_id}...")
    await asyncio.sleep(delay)  # Имитация асинхронной I/O операции
    print(f"[{time.time():.2f}] Данные {data_id} загружены")
    return {"id": data_id, "status": "loaded", "delay": delay}

async def main():
    print(f"[{time.time():.2f}] Запуск main...")
    # Создаем несколько задач, которые будут выполняться конкурентно
    task1 = asyncio.create_task(fetch_data(2, 1))
    task2 = asyncio.create_task(fetch_data(1, 2))

    print(f"[{time.time():.2f}] Делаем что-то параллельно, пока задачи выполняются...")
    # Ожидаем завершения всех задач
    results = await asyncio.gather(task1, task2)

    print(f"[{time.time():.2f}] Все задачи завершены. Результаты: {results}")

if __name__ == "__main__":
    asyncio.run(main())

Ключевые моменты:

  • Ключевое слово await используется для приостановки выполнения текущей корутины и передачи управления Event Loop, который может запустить другую задачу.
  • asyncio.run() запускает Event Loop и выполняет главную корутину.
  • Задачи (Tasks) выполняются конкурентно (псевдопараллельно), но не параллельно в истинном смысле (если нет многопоточности или многопроцессорности).
  • asyncio наиболее эффективен для I/O-bound задач (сеть, файлы, базы данных), где ожидание внешних ресурсов является основным узким местом, но не для CPU-bound задач (интенсивные вычисления), которые блокируют Event Loop.