asyncio

Материал из llmdocs — справочник разработчика
Портал: Python
asyncio
Типмодуль стандартной библиотеки
ЯзыкPython
Появился вPython 3.4 (2014)
Основные PEPPEP 3156, PEP 492
Модельоднопоточный цикл событий, кооперативная многозадачность
АльтернативыTrio, AnyIO, gevent
Документацияdocs.python.org/3/library/asyncio

asyncio — модуль стандартной библиотеки Python для написания конкурентного кода с помощью синтаксиса async/await. Он реализует цикл событий (event loop), который в одном потоке переключается между множеством задач, пока те ждут ввода-вывода: ответа сети, базы данных, файла. asyncio лежит в основе асинхронных веб-фреймворков (FastAPI, aiohttp), драйверов баз данных и многих клиентов API.

История

До asyncio асинхронный ввод-вывод в Python обеспечивали сторонние библиотеки — Twisted, Tornado, gevent — с несовместимыми между собой моделями. В 2012 году Гвидо ван Россум предложил единый стандарт (PEP 3156, кодовое имя Tulip), и в Python 3.4 (2014) модуль вошёл в стандартную библиотеку.[2] Первоначально сопрограммы писались на генераторах с yield from.

В Python 3.5 появились ключевые слова async def и await (PEP 492),[3] сделав сопрограммы отдельной сущностью языка. Дальнейшие важные изменения:

  • 3.7 — функция asyncio.run(), ставшая стандартной точкой входа;
  • 3.9 — asyncio.to_thread() для вызова блокирующего кода в потоке;
  • 3.11 — TaskGroup и asyncio.timeout(), структурированная конкурентность.[4]

Основные понятия

Сопрограмма (coroutine) — функция, объявленная через async def. Её вызов не выполняет код, а возвращает объект-сопрограмму; выполнение начинается, когда её ожидают через await или оборачивают в задачу.

Цикл событий — планировщик, который хранит готовые к выполнению задачи и следит за сокетами через механизмы ОС (epoll, kqueue, IOCP). Когда задача доходит до await на незавершённой операции, она добровольно отдаёт управление, и цикл запускает следующую. Это кооперативная многозадачность: переключение происходит только в точках await.

Задача (Task) — сопрограмма, запущенная в цикле событий конкурентно с другими (asyncio.create_task()).

Future — низкоуровневый объект «результат появится позже»; задачи являются его подклассом.

Пример

import asyncio
import time

async def fetch(name: str, delay: float) -> str:
    await asyncio.sleep(delay)          # имитация сетевого запроса
    return f"{name}: готово за {delay} с"

async def main() -> None:
    start = time.perf_counter()
    async with asyncio.TaskGroup() as tg:   # Python 3.11+
        tasks = [tg.create_task(fetch(f"запрос {i}", 1.0)) for i in range(3)]
    for t in tasks:
        print(t.result())
    print(f"всего: {time.perf_counter() - start:.1f} с")  # ≈ 1.0, а не 3.0

asyncio.run(main())

Три «запроса» по одной секунде выполняются примерно за секунду, потому что ожидание перекрывается. Если одна из задач в TaskGroup завершится исключением, остальные будут отменены, а ошибки собраны в ExceptionGroup.

Инструменты модуля

Средство Назначение
asyncio.gather() дождаться нескольких awaitable и собрать результаты
asyncio.TaskGroup структурированный запуск группы задач с отменой при ошибке
asyncio.timeout(), wait_for() ограничение времени ожидания
asyncio.Queue очередь производитель — потребитель
asyncio.Semaphore, Lock, Event примитивы синхронизации
asyncio.to_thread() выполнить блокирующую функцию в отдельном потоке
loop.run_in_executor() то же для пула потоков или процессов
потоки (open_connection) высокоуровневая работа с TCP

Типичные ошибки

  • Блокирующий вызов внутри сопрограммы. time.sleep(), синхронный HTTP-клиент или тяжёлый расчёт останавливают весь цикл событий. Их нужно заменять асинхронными аналогами или выносить в to_thread().
  • Забытый await. Вызов сопрограммы без await ничего не делает; Python выдаёт предупреждение «coroutine was never awaited».
  • Потерянные задачи. Задача, созданная create_task() без сохранения ссылки, может быть удалена сборщиком мусора; документация рекомендует хранить ссылки или использовать TaskGroup.[1]
  • Ожидание ускорения расчётов. asyncio не распараллеливает вычисления на ядра процессора — для этого нужны процессы или сборка без GIL (см. GIL и free-threading (PEP 703)).

Режим отладки (asyncio.run(main(), debug=True) или переменная окружения PYTHONASYNCIODEBUG=1) сообщает о медленных обратных вызовах и неожиданных сопрограммах.[5]

asyncio, потоки и процессы

Подход Когда подходит
asyncio тысячи одновременных сетевых соединений, I/O-нагрузка
потоки (threading) умеренное число блокирующих операций, совместимость со старыми библиотеками
процессы (multiprocessing) вычисления на нескольких ядрах

Подробнее о различии понятий — в статье Конкурентность и параллелизм.

Альтернативы

Trio предложил идею «детских садов» (nurseries) — предшественника TaskGroup. AnyIO даёт единый API поверх asyncio и Trio. uvloop — замена стандартного цикла событий на основе libuv, ускоряющая сетевой код.

См. также

Источники

  1. asyncio — Asynchronous I/O (Python documentation)
  2. PEP 3156 — Asynchronous IO Support Rebooted: the asyncio Module
  3. PEP 492 — Coroutines with async and await syntax
  4. Coroutines and Tasks — Task Groups
  5. Developing with asyncio