Вернуться к статьям

Асинхронное программирование в Python: практические паттерны, ошибки и решения

Программирование 14.02.2026 6 просмотров

Ключевые слова

python asyncio асинхронное программирование async await aiohttp паттерны отладка ошибки I/O python async event loop лучшие практики тестирование производительность
Асинхронное программирование в Python: практические паттерны, ошибки и решения

Асинхронное программирование становится всё важнее для современного Python-разработчика. Использование asyncio и ключевых слов async/await позволяет писать производительные приложения (от веб-серверов до парсеров и сервисов), не усложняя архитектуру потоками или процессами. В этой статье разберём практические паттерны асинхронного кода, частые ошибки и способы их избежать — с примерами и объяснениями.

Зачем нужна асинхронность в Python?

Обычное (синхронное) выполнение блокирует поток на каждом вводе-выводе: например, при ожидании ответа от сервера, файла или базы данных. В асинхронном подходе такие «ожидания» можно использовать разумнее — переключаясь на выполнение других задач, пока операция не завершилась.

  • Примеры применимости:
    • Одновременные HTTP-запросы к API
    • Параллельная обработка большого числа файлов
    • Веб-серверы (например, на базе FastAPI, Starlette, aiohttp)

Какую выгоду это даёт?

  1. Более высокая производительность на I/O-зависимых задачах.
  2. Упрощение конструкции (нет необходимости в потоках, блокировках, синхронизации).
  3. Лучшее использование одного процесса Python — несмотря на GIL.

Базовые паттерны и примеры асинхронного кода

1. Асинхронные функции и await

Асинхронные функции объявляются с помощью async def и используются с await:

import asyncio

async def fetch_data():
    print('Старт')
    await asyncio.sleep(1)
    print('Готово!')

asyncio.run(fetch_data())

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

  • asyncio.sleep() — типичный асинхронный эквивалент time.sleep().
  • При await управление возвращается event loop'у, другие задачи продолжают работу.

2. Одновременное выполнение задач

Вместо последовательного выполнения можно запускать множество задач одновременно:

import asyncio

async def download(url):
    print(f'Скачиваю {url}')
    await asyncio.sleep(1)
    print(f'Готово: {url}')

async def main():
    urls = ['a.com', 'b.com', 'c.com']
    tasks = [asyncio.create_task(download(u)) for u in urls]
    await asyncio.gather(*tasks)

asyncio.run(main())

Всё происходит в одном потоке, но задачи сами сдают управление во время ожидания.

3. Использование асинхронных библиотек

  • aiohttp — для HTTP-клиентов и серверов
  • aiosqlite — асинхронный доступ к SQLite
  • asyncpg — высокопроизводительный PostgreSQL-клиент

Пример с aiohttp:

import aiohttp
import asyncio

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

async def main():
    html = await fetch('https://python.org')
    print(len(html))

asyncio.run(main())

Частые ошибки и как их избежать

1. Смешивание sync и async: блокировки цикла

Не используйте в асинхронных функциях синхронных долгих операций (например, time.sleep() или CPU-bound вычислений). Это блокирует event loop, мешая масштабируемости.

  • await asyncio.sleep(1) вместо time.sleep(1)
  • Для тяжёлых вычислений — вынесите их в ThreadPool или ProcessPool:
import asyncio

def sync_work():
    ... # CPU-bound функция

async def main():
    loop = asyncio.get_running_loop()
    result = await loop.run_in_executor(None, sync_work)

2. Неправильное создание и использование задач

  • Не забывайте запускать асинхронные функции через asyncio.run() (или внутри ивент-лупа).
  • Не используйте низкоуровневые API (asyncio.get_event_loop()) без необходимости — предпочтительно asyncio.run() (Python 3.7+).

3. Исключения теряются в фоне

Если создать задачу через asyncio.create_task() и не дождаться её — исключения в ней могут остаться незамеченными. Всегда ждите await для создания/обработки ошибок:

task = asyncio.create_task(foo())
try:
    await task
except Exception as ex:
    print('Ошибка:', ex)

4. Неочевидное поведение gather/cancel

  • asyncio.gather() не отменяет остальные задачи при исключении по умолчанию.
  • Используйте return_exceptions=True для обработки всех ошибок:
results = await asyncio.gather(*tasks, return_exceptions=True)

Рекомендации и лучшие практики

Архитектура асинхронных проектов

  • Строго отделяйте синхронный и асинхронный код.
  • Используйте асинхронные библиотеки «по цепочке» (например, aiohttp + aioredis).
  • Обрабатывайте исключения и отмену задач (cancellation).
  • Проводите нагрузочное тестирование: реальную производительность видно только при параллельных запросах.

Инструменты для отладки и тестирования

  • Базовый дебаг — через print() и logging (ложите логи до и после await)
  • Модули pytest-asyncio и pytest для тестирования async-функций
  • async-timeout для явного контроля таймаутов асинхронных операций

Заключение

Асинхронное программирование в Python открывает возможности для масштабируемых сервисов и эффективной обработки задач, связанных с вводом-выводом. Осваивайте его шаг за шагом:

  1. Начните с async/await и старта простых задач.
  2. Постепенно интегрируйте асинхронные библиотеки в проекты.
  3. Следите за типичными ошибками и тестируйте свой код на исправность.

Совет: используйте асинхронность там, где действительно ждёте внешних ресурсов, а не везде подряд. 

Помните — грамотная работа с асинхронным кодом ведёт к чистому, предсказуемому и быстрому Python-приложению.