Мы рассмотрим основы asyncio, осваивая сопрограммы, задачи и циклы событий с подробными примерами.

Контекст

Для исторического контекста вы должны знать, что asyncio был представлен в Python 3.4 как предварительный модуль и благодаря его широкому распространению с тех пор стал стандартной библиотекой в ​​Python 3.7. Он был разработан для решения проблем асинхронного программирования на Python и упрощения написания параллельного кода. До asyncio разработчикам приходилось полагаться на многопоточность, многопроцессорность или сторонние библиотеки, такие как Twisted или Tornado.

Введение

Asyncio — это инфраструктура асинхронного ввода-вывода, позволяющая писать параллельный код с использованием синтаксиса async и await. Он основан на цикле событий, который отвечает за управление операциями ввода-вывода, выполнение сопрограмм и обработку других асинхронных задач.

Настройка среды

Чтобы начать использовать asyncio, у вас должен быть установлен Python 3.7 или выше. Вы можете проверить свою версию Python, выполнив следующую команду в терминале или командной строке:

python --version

Если вам нужно установить или обновить Python, посетите официальный сайт Python (https://www.python.org/downloads/), чтобы загрузить последнюю версию.

Базовые концепты

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

Цикл событий

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

Корутины

Сопрограммы — это строительные блоки асинхронного кода в asyncio. Это функции, определенные с синтаксисом async def, и их можно приостанавливать и возобновлять во время выполнения. Корутины используют ключевое слово await, чтобы вернуть управление циклу обработки событий, позволяя другим задачам выполняться одновременно.

Простое асинхронное приложение

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

import asyncio

async def greet(name, delay):
    await asyncio.sleep(delay)
    print(f"Hello, {name}!")

async def main():
    await asyncio.gather(
        greet("Alice", 2),
        greet("Bob", 1),
        greet("Charlie", 3),
    )

if __name__ == "__main__":
    asyncio.run(main())

Выход

Hello, Bob!
Hello, Alice!
Hello, Charlie!

Мы определили сопрограмму greet(), которая принимает имя и задержку в качестве параметров. Он засыпает на заданную задержку, используя await asyncio.sleep(delay), а затем печатает приветственное сообщение. Сопрограмма main() собирает все сопрограммы приветствия и запускает их одновременно, используя asyncio.gather(). Наконец, мы используем asyncio.run(main()) для выполнения основной сопрограммы и запуска цикла событий. Обратите внимание, что печать происходит асинхронно из-за задержки.

Работа с задачами и сопрограммами

Далее давайте создадим задачи для планирования выполнения сопрограмм. Задачи создаются с помощью функции asyncio.create_task() и могут ожидаться как сопрограммы.

import asyncio

async def long_operation(n):
    print(f"Starting operation {n}")
    await asyncio.sleep(n)
    print(f"Finished operation {n}")

async def main():
    task1 = asyncio.create_task(long_operation(2))
    task2 = asyncio.create_task(long_operation(4))

    # Wait for tasks to complete
    await task1
    await task2

if __name__ == "__main__":
    asyncio.run(main())

Выход

Starting operation 2
Starting operation 4
Finished operation 2
Finished operation 4

Мы создали две задачи, task1 и task2, которые запускают сопрограмму long_operation() с разными параметрами. Затем мы ждем обе задачи, чтобы гарантировать их завершение до завершения основной сопрограммы.

Обработка ошибок и тайм-ауты

Вы можете обрабатывать исключения в сопрограммах asyncio, используя стандартные блоки try и except. Кроме того, вы можете использовать asyncio.wait_for(), чтобы установить время ожидания для завершения сопрограмм.

import asyncio

async def might_fail():
    try:
        await asyncio.sleep(2)
        print("Success!")
    except asyncio.CancelledError:
        print("Operation cancelled")

async def main():
    task = asyncio.create_task(might_fail())

    try:
        await asyncio.wait_for(task, timeout=1)
    except asyncio.TimeoutError:
        print("Operation timed out")
        task.cancel()
        await task

if __name__ == "__main__":
    asyncio.run(main())

Выход

Operation timed out
Operation cancelled

Здесь мы используем asyncio.wait_for(), чтобы установить тайм-аут в 1 секунду для сопрограммы might_fail(). Если сопрограмма не завершается в течение тайм-аута, возникает asyncio.TimeoutError. Затем мы отменяем задачу и ждем ее, чтобы обеспечить правильную отмену.

Использование asyncio с сетью

Asyncio упрощает работу с сетью, используя асинхронный ввод-вывод. Давайте рассмотрим пример эхо-сервера, использующего StreamReader и StreamWriter asyncio:

import asyncio

async def echo(reader, writer):
    while True:
        data = await reader.read(100)
        if not data:
            break

        writer.write(data)
        await writer.drain()

    writer.close()
    await writer.wait_closed()

async def main():
    server = await asyncio.start_server(echo, "127.0.0.1", 8888)

    async with server:
        await server.serve_forever()

if __name__ == "__main__":
    asyncio.run(main())

В этом примере мы создаем эхо-сервер, который прослушивает порт 8888. Сопрограмма echo() обрабатывает входящие соединения, считывает данные из StreamReader и записывает те же данные обратно в StreamWriter. Сервер запускается с использованием asyncio.start_server() и бесконечно работает с server.serve_forever().

asyncio и сторонние библиотеки

Чтобы в полной мере использовать возможности asyncio, важно знать, как работать со сторонними библиотеками, поддерживающими асинхронное программирование. Многие популярные библиотеки были разработаны или обновлены для поддержки asyncio, например aiohttp для HTTP-клиентов и серверов, aiomysql для MySQL и aioredis для Redis.

Пример: использование aiohttp

Чтобы продемонстрировать, как использовать asyncio со сторонними библиотеками, давайте создадим простое приложение, которое извлекает данные из API с помощью aiohttp. Во-первых, вам нужно установить библиотеку aiohttp:

pip install aiohttp

Затем создайте следующий скрипт Python:

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():
    url = "https://jsonplaceholder.typicode.com/todos/1"
    result = await fetch(url)
    print(result)

if __name__ == "__main__":
    asyncio.run(main())

В этом примере мы определяем сопрограмму fetch(), которая принимает URL-адрес в качестве параметра. С помощью aiohttp.ClientSession() выполняем асинхронный GET-запрос и читаем текст ответа. Сопрограмма main() ожидает сопрограмму fetch() и печатает ответ.

Отладка и профилирование

Отладка и профилирование асинхронных приложений могут немного отличаться от традиционных синхронных приложений. К счастью, Python предоставляет встроенные инструменты и функции, помогающие отлаживать и профилировать асинхронный код.

Отладка

Чтобы включить режим отладки asyncio, установите для переменной среды PYTHONASYNCIODEBUG значение 1 или вызовите asyncio.set_debug(True) перед запуском цикла обработки событий. Это активирует дополнительные проверки и предупреждения, которые помогут вам выявить проблемы в вашем коде.

Профилирование

Профилировать приложения asyncio можно с помощью встроенного в Python модуля cProfile или сторонних инструментов, таких как py-spy. При использовании cProfile вы можете профилировать свой цикл событий, выполнив следующую команду:

python -m cProfile your_script.py

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

Лучшие практики для Asyncio

Наконец, давайте рассмотрим некоторые рекомендации по использованию asyncio в Python:

  1. Используйте ключевые слова async def и await для определения сопрограмм и приостановки их выполнения.
  2. Используйте asyncio.gather() или asyncio.create_task() для одновременного запуска сопрограмм.
  3. Правильно обрабатывать исключения с помощью блоков try и except.
  4. Используйте asyncio.wait_for(), чтобы установить время ожидания для сопрограмм.
  5. Всегда закрывайте ресурсы, такие как сетевые подключения или дескрипторы файлов, когда они больше не нужны.
  6. Используйте сторонние библиотеки, поддерживающие asyncio, для повышения производительности и совместимости.
  7. Включите режим отладки asyncio и используйте инструменты профилирования для выявления и устранения проблем в коде.

Следуя этим рекомендациям, вы сможете создавать эффективные, масштабируемые и удобные в сопровождении асинхронные приложения на Python.

Заключение

В этом руководстве мы изучили библиотеку asyncio в Python, охватив ее исторический контекст, основные концепции и различные функции. Мы продемонстрировали, как создавать задачи и управлять ими, обрабатывать ошибки, реализовывать тайм-ауты, работать с сетью и интегрировать сторонние библиотеки. Кроме того, мы обсудили лучшие практики, методы отладки и профилирования, которые помогут вам создавать эффективные, масштабируемые и удобные в сопровождении асинхронные приложения.

Примечание от меня!

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

Идите вперед и погрузитесь глубже в библиотеку asyncio и изучите ее многочисленные функции, поскольку она, несомненно, станет бесценным инструментом в вашем наборе инструментов разработчика Python. И если вы хотите прочитать больше: Использование Asyncio в Python