Основы asyncio в Python
В предыдущей статье разбирали потоки и процессы. У них общая модель: ОС переключает между «исполнителями». asyncio идёт другим путём: один поток, кооперативная многозадачность. Программа сама помечает места, где можно «отложить» задачу. Эти места обозначаются ключевым словом await.
Для I/O-bound нагрузок asyncio даёт лучшее соотношение производительности и ресурсов: тысячи одновременных соединений на одном потоке без накладных расходов на потоки ОС.
Цикл событий и кооперативная модель
Сердце asyncio — event loop (цикл событий). Он держит список задач, выполняет одну из них, и когда задача доходит до await something_slow(), задача «уступает управление», event loop переключается на следующую готовую задачу. Когда something_slow() завершается, исходная задача снова становится готовой.

Важно: переключение происходит только на await. Никаких прерываний посередине вычисления. Это «кооперативная» многозадачность: задачи договариваются, когда уступать. У такого подхода есть последствие: если задача не делает await (например, считает что-то долго на CPU), весь event loop стоит.
async и await
В Python 3.5 появились ключевые слова для асинхронности:
- async def — определяет корутину (асинхронную функцию)
- await — внутри корутины: «дождись завершения этой операции, на время ожидания отпусти управление»
Python 3.13import asyncio async def say_hello(): print("Привет...") await asyncio.sleep(1) # не блокирует поток, отпускает event loop print("...мир")
Важный нюанс: вызов say_hello() не запускает корутину. Он создаёт объект-корутину:
Python 3.13coro = say_hello() print(type(coro)) # <class 'coroutine'> # код корутины ещё не выполнен!
Чтобы запустить корутину, нужен event loop.
asyncio.run: точка входа
asyncio.run() запускает event loop, выполняет переданную корутину и закрывает loop:
Python 3.13import asyncio async def main(): print("Запуск") await asyncio.sleep(1) print("Завершено через 1 секунду") asyncio.run(main())
asyncio.run() — стандартный способ запустить async-программу из обычного синхронного кода. Внутри одной программы вызывается один раз на верхнем уровне.
Последовательно vs конкурентно
Если просто пишем await подряд, корутины выполняются последовательно, одна за другой:
Python 3.13import asyncio import time async def slow_task(name, delay): await asyncio.sleep(delay) print(f"{name} готова за {delay}с") async def main(): start = time.time() await slow_task("A", 2) await slow_task("B", 1) await slow_task("C", 3) print(f"Всего: {time.time() - start:.1f}с") # ~6с asyncio.run(main())
Все три задачи могли бы идти параллельно (они только ждут), но мы заставили их идти по очереди: await ждёт завершения текущей. Чтобы запустить конкурентно, используем asyncio.gather():
Python 3.13async def main(): start = time.time() await asyncio.gather( slow_task("A", 2), slow_task("B", 1), slow_task("C", 3), ) print(f"Всего: {time.time() - start:.1f}с") # ~3с asyncio.run(main())
gather() запускает все переданные корутины конкурентно и возвращает список результатов. Общее время равно самой долгой задаче, а не сумме всех. Это и есть суть asyncio для I/O.
Tasks: запуск корутин «в фоне»
Иногда нужно запустить корутину «прямо сейчас», не дожидаясь её, чтобы она работала параллельно с основной логикой. Для этого есть asyncio.create_task():
Python 3.13import asyncio async def background_log(): while True: print("heartbeat") await asyncio.sleep(1) async def main(): task = asyncio.create_task(background_log()) await asyncio.sleep(3) # делаем что-то другое task.cancel() # остановили фоновую корутину asyncio.run(main())
create_task() сразу планирует корутину к выполнению. Возвращает объект Task, у которого есть методы cancel(), done(), result(). По сути Task — это корутина, которую event loop уже запустил и отслеживает: можно проверить статус, забрать результат или отменить её.
Главные правила
- Внутри async def любое долгое ожидание — через await. Обычный time.sleep(1) заблокирует весь event loop. Используйте await asyncio.sleep(1).
- Хочется конкурентно — asyncio.gather() или asyncio.create_task(). Просто await подряд = последовательно.
- CPU-bound в asyncio останавливает всё. Считаете долго? Выносите в run_in_executor (следующая статья) или в multiprocessing.
Проверка понимания
Что такое основная особенность асинхронного программирования в asyncio?
В следующей статье — продвинутые техники asyncio: очереди, синхронизация между корутинами и (главное) как запускать блокирующий код, не убивая event loop.
