Как использовать asyncio для асинхронного кода

онлайн тренажер по питону
Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое асинхронное программирование и зачем оно нужно

Асинхронное программирование позволяет выполнять несколько операций одновременно без необходимости ожидания завершения каждой из них. Это критически важно для создания высокопроизводительных приложений, которые не блокируются при выполнении медленных операций.

Основные преимущества асинхронности

Асинхронность особенно эффективна в следующих сценариях:

  • Сетевые запросы (HTTP-запросы, работа с REST API)
  • Операции с файловой системой (чтение и запись больших файлов)
  • Взаимодействие с базами данных (SQL-запросы, NoSQL операции)
  • WebSocket соединения и real-time приложения
  • Парсинг веб-страниц и скрапинг данных
  • Обработка очередей сообщений

Введение в библиотеку asyncio

asyncio — это стандартная библиотека Python для написания асинхронного кода, основанного на событийном цикле (event loop). Библиотека входит в состав Python начиная с версии 3.4 и была значительно улучшена в версиях 3.7+.

Ключевые возможности asyncio

С помощью asyncio можно:

  • Создавать и управлять корутинами
  • Запускать множественные асинхронные задачи параллельно
  • Эффективно обрабатывать тысячи одновременных соединений
  • Интегрироваться с асинхронными библиотеками и фреймворками

Основные концепции asyncio

Корутины (Coroutines)

Корутина — это специальная функция, которая может быть приостановлена и позже возобновлена. В Python корутины определяются с помощью ключевого слова async def.

import asyncio

async def greet():
    print("Привет!")
    await asyncio.sleep(1)
    print("Прошло 1 секунда.")

# Запуск корутины
asyncio.run(greet())

Ключевое слово await

await используется для приостановки выполнения корутины до завершения другой асинхронной операции. Это ключевой механизм передачи управления обратно в event loop.

async def delayed_message():
    print("Начало выполнения")
    await asyncio.sleep(2)
    print("Сообщение через 2 секунды")

asyncio.run(delayed_message())

Event Loop (Цикл событий)

Event Loop — это сердце asyncio, механизм, который управляет выполнением всех асинхронных задач. Начиная с Python 3.7, рекомендуется использовать asyncio.run() для запуска асинхронного кода.

# Современный способ (Python 3.7+)
asyncio.run(main_coroutine())

# Устаревший способ (до Python 3.7)
loop = asyncio.get_event_loop()
loop.run_until_complete(main_coroutine())

Создание и управление асинхронными задачами

Параллельное выполнение задач

Асинхронный код раскрывает свою мощь при параллельном выполнении нескольких задач:

async def task(name, delay):
    print(f"Задача {name} начата")
    await asyncio.sleep(delay)
    print(f"Задача {name} завершена после {delay} секунд")
    return f"Результат от {name}"

async def main():
    # Способ 1: asyncio.gather()
    results = await asyncio.gather(
        task("A", 2),
        task("B", 1),
        task("C", 3)
    )
    print("Все результаты:", results)

asyncio.run(main())

Создание задач с asyncio.create_task()

Для лучшего контроля над выполнением используйте asyncio.create_task():

async def main():
    # Создаем задачи
    task_a = asyncio.create_task(task("A", 2))
    task_b = asyncio.create_task(task("B", 1))
    task_c = asyncio.create_task(task("C", 3))
    
    # Ожидаем их завершения
    await task_a
    await task_b
    await task_c

asyncio.run(main())

Асинхронные HTTP-запросы

Установка и использование aiohttp

Для работы с HTTP-запросами в асинхронном коде используйте библиотеку aiohttp:

pip install aiohttp

Простой GET-запрос

import aiohttp
import asyncio

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

async def main():
    html = await fetch_url('https://httpbin.org/delay/1')
    print(f"Получено {len(html)} символов")

asyncio.run(main())

Множественные HTTP-запросы

async def fetch_multiple_urls():
    urls = [
        'https://httpbin.org/delay/1',
        'https://httpbin.org/delay/2',
        'https://httpbin.org/json'
    ]
    
    async with aiohttp.ClientSession() as session:
        tasks = [session.get(url) for url in urls]
        responses = await asyncio.gather(*tasks)
        
        for response in responses:
            print(f"Status: {response.status}, URL: {response.url}")
            response.close()

asyncio.run(fetch_multiple_urls())

Обработка ошибок и исключений

Базовая обработка исключений

async def risky_task():
    try:
        await asyncio.sleep(1)
        raise ValueError("Что-то пошло не так!")
    except ValueError as e:
        print(f"Обработана ошибка: {e}")
        return "Задача завершена с ошибкой"

asyncio.run(risky_task())

Обработка ошибок в группе задач

async def task_with_error(name, should_fail=False):
    await asyncio.sleep(1)
    if should_fail:
        raise Exception(f"Ошибка в задаче {name}")
    return f"Успех: {name}"

async def main():
    tasks = [
        asyncio.create_task(task_with_error("A", False)),
        asyncio.create_task(task_with_error("B", True)),
        asyncio.create_task(task_with_error("C", False))
    ]
    
    # return_exceptions=True позволяет получить исключения как результаты
    results = await asyncio.gather(*tasks, return_exceptions=True)
    
    for i, result in enumerate(results):
        if isinstance(result, Exception):
            print(f"Задача {i} завершилась с ошибкой: {result}")
        else:
            print(f"Задача {i} успешна: {result}")

asyncio.run(main())

Работа с файлами асинхронно

Использование aiofiles

pip install aiofiles
import aiofiles
import asyncio

async def read_file_async(filename):
    async with aiofiles.open(filename, 'r', encoding='utf-8') as file:
        content = await file.read()
        return content

async def write_file_async(filename, data):
    async with aiofiles.open(filename, 'w', encoding='utf-8') as file:
        await file.write(data)

async def main():
    # Запись файла
    await write_file_async('test.txt', 'Привет, асинхронный мир!')
    
    # Чтение файла
    content = await read_file_async('test.txt')
    print(f"Содержимое файла: {content}")

asyncio.run(main())

Продвинутые техники asyncio

Семафоры для ограничения конкуренции

async def limited_task(semaphore, name):
    async with semaphore:
        print(f"Задача {name} начала выполнение")
        await asyncio.sleep(2)
        print(f"Задача {name} завершена")

async def main():
    # Ограничиваем выполнение максимум 2 задач одновременно
    semaphore = asyncio.Semaphore(2)
    
    tasks = [
        limited_task(semaphore, f"Task-{i}")
        for i in range(5)
    ]
    
    await asyncio.gather(*tasks)

asyncio.run(main())

Таймауты и отмена задач

async def long_running_task():
    try:
        await asyncio.sleep(10)
        return "Задача завершена"
    except asyncio.CancelledError:
        print("Задача была отменена")
        raise

async def main():
    try:
        # Ограничиваем выполнение 3 секундами
        result = await asyncio.wait_for(long_running_task(), timeout=3.0)
        print(result)
    except asyncio.TimeoutError:
        print("Задача превысила лимит времени")

asyncio.run(main())

Когда использовать asyncio

Идеальные сценарии применения

asyncio наиболее эффективен в следующих случаях:

  • Web-разработка: создание асинхронных веб-серверов и API
  • Сетевые приложения: чаты, мессенджеры, real-time приложения
  • Парсинг и скрапинг: обработка множества веб-страниц одновременно
  • IoT и микросервисы: обработка большого количества мелких запросов
  • Телеграм-боты: обработка множества пользователей одновременно

Метрики производительности

Сравнение синхронного и асинхронного кода для 10 HTTP-запросов:

import time
import requests
import aiohttp
import asyncio

# Синхронная версия
def sync_requests():
    start_time = time.time()
    for i in range(10):
        response = requests.get('https://httpbin.org/delay/1')
    end_time = time.time()
    print(f"Синхронно: {end_time - start_time:.2f} секунд")

# Асинхронная версия
async def async_requests():
    start_time = time.time()
    async with aiohttp.ClientSession() as session:
        tasks = [
            session.get('https://httpbin.org/delay/1')
            for i in range(10)
        ]
        responses = await asyncio.gather(*tasks)
        for response in responses:
            response.close()
    end_time = time.time()
    print(f"Асинхронно: {end_time - start_time:.2f} секунд")

# sync_requests()  # ~10 секунд
# asyncio.run(async_requests())  # ~1 секунда

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

Что следует делать

  1. Используйте asyncio.run() для запуска асинхронного кода в Python 3.7+
  2. Применяйте async with для асинхронных контекстных менеджеров
  3. Используйте await asyncio.sleep() вместо time.sleep()
  4. Группируйте задачи с помощью asyncio.gather() или asyncio.create_task()
  5. Обрабатывайте исключения правильно в асинхронном коде

Чего следует избегать

  1. Не вызывайте блокирующие функции внутри асинхронного кода без обёрток
  2. Не смешивайте requests с asyncio — используйте aiohttp
  3. Не игнорируйте обработку исключений в асинхронных задачах
  4. Не создавайте слишком много одновременных задач без ограничений

Интеграция с популярными библиотеками

Асинхронные ORM

# Пример с SQLAlchemy (async)
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession

async def database_example():
    engine = create_async_engine("sqlite+aiosqlite:///example.db")
    
    async with AsyncSession(engine) as session:
        # Асинхронные операции с БД
        result = await session.execute("SELECT * FROM users")
        users = result.fetchall()
        return users

Веб-фреймворки

Популярные асинхронные фреймворки:

  • FastAPI: современный, быстрый веб-фреймворк
  • Starlette: легковесный ASGI фреймворк
  • AIOHTTP: полнофункциональный веб-фреймворк
  • Sanic: быстрый веб-фреймворк, вдохновленный Flask

Часто задаваемые вопросы

Можно ли использовать asyncio с потоками и процессами?

Да, asyncio предоставляет методы для интеграции с синхронным кодом:

import asyncio
import concurrent.futures

def cpu_intensive_task(n):
    # Тяжелая вычислительная задача
    return sum(i * i for i in range(n))

async def main():
    loop = asyncio.get_running_loop()
    
    # Выполнение в пуле потоков
    with concurrent.futures.ThreadPoolExecutor() as pool:
        result = await loop.run_in_executor(pool, cpu_intensive_task, 100000)
        print(f"Результат: {result}")

asyncio.run(main())

Чем отличается asyncio от многопоточности?

Asyncio (однопоточность):

  • Работает в одном потоке
  • Управляется событийным циклом
  • Эффективен для I/O операций
  • Нет проблем с GIL
  • Меньше накладных расходов

Threading (многопоточность):

  • Использует несколько потоков ОС
  • Подходит для CPU-интенсивных задач
  • Ограничен GIL в Python
  • Больше накладных расходов на переключение контекста

Как тестировать асинхронный код?

import pytest
import asyncio

# Использование pytest-asyncio
@pytest.mark.asyncio
async def test_async_function():
    result = await async_function()
    assert result == "expected_value"

# Или с unittest
import unittest

class TestAsyncCode(unittest.TestCase):
    def test_async_function(self):
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        result = loop.run_until_complete(async_function())
        self.assertEqual(result, "expected_value")
        loop.close()

Как работать с asyncio в Jupyter Notebook?

В Jupyter Notebook может потребоваться дополнительная настройка:

import nest_asyncio
nest_asyncio.apply()

# Теперь можно использовать asyncio.run() в Jupyter
async def notebook_example():
    await asyncio.sleep(1)
    return "Работает в Jupyter!"

asyncio.run(notebook_example())

Заключение

Асинхронное программирование с помощью asyncio — это мощный инструмент для создания высокопроизводительных Python-приложений. Библиотека позволяет эффективно обрабатывать I/O операции, создавать отзывчивые веб-сервисы и масштабируемые сетевые приложения.

Ключевые преимущества asyncio:

  • Высокая производительность для I/O-интенсивных задач
  • Эффективное использование системных ресурсов
  • Простота масштабирования приложений
  • Богатая экосистема асинхронных библиотек

Освоив основы asyncio, вы сможете создавать современные, быстрые и эффективные приложения, которые могут обрабатывать тысячи одновременных операций без блокировки основного потока выполнения.

Новости