Python async / await Объяснение: руководство 2026 г.
Асинхронность в Python имеет репутацию запутанной системы. Это не обязательно. Как только вы усвоите одно предложение — async позволяет ОДНОМУ потоку выполнять несколько чередующихся задач, связанных с вводом-выводом — всё остальное следует за ним.
В этом руководстве рассказывается: что на самом деле делают async def и await, когда побеждает async, а когда это неправильный инструмент, четыре шаблона, которые использует каждая асинхронная кодовая база (последовательный, сбор, семафор, тайм-аут), и три способа, которыми новички молча проглатывают ошибки.
Ментальная модель, состоящая из одного предложения
Асинхронность в Python — это совместный параллелизм в одном потоке. Когда ваш код нажимает «await», управление возвращается в цикл событий, который может запустить другую задачу, пока первая ожидает ввода-вывода. Когда ввод-вывод завершается, цикл возобновляет выполнение первой задачи с того места, где он был приостановлен.
Вот и все. Никаких потоков, никаких процессов, никакого параллелизма (в смысле CPython с GIL). Всего один поток, совмещающий множество задач, которые большую часть времени проводят в ожидании в сети/на диске/базе данных.
Асинхронность работает быстрее, когда узкое место ждет, а не выполняет вычисления.
Трехстрочная минимально жизнеспособная асинхронная программа
async def hello()— объявляет сопрограмму. Вызов hello() не запускает код; он создает объект сопрограммы.await asyncio.sleep(1)— возвращает управление циклу событий на 1 секунду.asyncio.run(hello())— создает цикл событий и запускает сопрограмму до завершения.
Правило: `await` только внутри `async def`. Вызов await в обычной функции является синтаксической ошибкой.
Когда побеждает асинхронность (а когда нет)
Async побеждает: работа, связанная с вводом-выводом
Любая задача, которая тратит большую часть времени на ОЖИДАНИЕ, является кандидатом на асинхронность:
- HTTP-запросы (внешние API, парсинг, веб-перехватчики)
- Запросы к базе данных
- Файл читает/пишет
- потоки WebSocket/gRPC
- Потребители очередей (Redis, RabbitMQ, Kafka)
Пример: получение 100 URL-адресов.
Ускорение настенных часов в 50–100 раз для работы с интенсивным вводом-выводом. Именно здесь асинхронность зарабатывает свой бюджет сложности.
Async LOSES: работа с загрузкой процессора
Async не обеспечивает параллелизма вычислений. Поскольку он является кооперативным однопоточным, цикл «горячего» процессора блокирует весь цикл событий.
Все остальные задачи «зависают», пока работает pbkdf2. Для работы с привязкой к процессору используйте «многопроцессорность» или «concurrent.futures.ProcessPoolExecutor», а не асинхронность.
Смешанная рабочая нагрузка: run_in_executor
Если ОДИН шаг нагружает ЦП, а остальные — ввод-вывод, оберните шаг ЦП так, чтобы он выполнялся в пуле потоков, не блокируя цикл:
None = пул потоков по умолчанию. Отлично подходит для вызова сторонних библиотек синхронизации, которые работают медленно, но их нельзя переписать.
Четыре шаблона
1. Последовательный — по одной задаче за раз
Используйте, когда каждый запрос зависит от результата предыдущего.
2. asyncio.gather — запустить все сразу
Выбор по умолчанию для независимых задач ввода-вывода. Но: если вы соберете 10 000 задач для одного API, вы получите файловые дескрипторы с ограничением скорости или исчерпывающим числом операций. Вот здесь-то и появляется шаблон семафора.
3. Семафор — ограничение параллелизма
asyncio.Semaphore(N) позволяет не более N задачам пройти мимо async with sem: в любой момент. Остальные задачи стоят в очереди. Используйте для любого внешнего API с ограничением скорости или базы данных с пулом соединений.
4. Таймаут — отказаться от медленной работы
asyncio.wait_for отменяет базовую задачу через N секунд. Важно для любого асинхронного кода, который взаимодействует с сетью — никогда не делайте неограниченное ожидание.
Три ловушки, допускающие ошибки
Ловушка 1: asyncio.gather с return_Exceptions=True
Полезно, если вы хотите сохранить частичные результаты, но СМЕРТЕЛЬНО, если вы не проверяете каждый результат:
Без проверки ошибки молча исчезают.
Ловушка 2: задачи «выстрелил и забыл»
Если background_work() вызывается, Python выдает предупреждение при завершении работы и продолжает работу. Если вас что-то волнует, сохраните задачу и «ожидайте» ее или используйте «asyncio.TaskGroup» (3.11+), который распространяет все ошибки.
Ловушка 3: код синхронизации внутри async
запросы синхронны. Даже внутри async def, requests.get блокирует весь цикл до тех пор, пока не придет ответ. Используйте httpx (или aiohttp) для асинхронного HTTP.
Правило: никогда не используйте библиотеку синхронного ввода-вывода внутри асинхронного кода. Если у библиотеки нет асинхронного варианта, оберните ее в run_in_executor.
asyncio.TaskGroup — способ разветвления 2026 года
В Python 3.11 добавлена TaskGroup, которая четко решает проблему «выстрелил и забыл»:
Структурированный параллелизм — ни потерянных ошибок, ни потерянных задач. Предпочитайте это вместо простого create_task в современном коде.
Пример из реальной жизни: парсер с ограниченной скоростью
Все четыре шаблона в 15 строках: ограниченный параллелизм (семафор), таймауты (wait_for), явные ветки ошибок (без поглощения) и сборка для разветвления. Скопируйте и вставьте эту фигуру в любую задачу асинхронного ввода-вывода.
Асинхронность в Python требует серьезного обучения, но мысленная модель проста: один поток, совместные паузы в ожидании, ввод-вывод перекрывается. Освойте gather + Semaphore + wait_for + TaskGroup, и вы выполните 95 % асинхронной работы, которую когда-либо писали.
Следующий шаг: Старший курс Python содержит 20 практических уроков по asyncio, TaskGroup, aiohttp/httpx и описанным выше шаблонам параллелизма — при этом настоящий Python работает в вашем браузере.