Асинхронное программирование: старт

Модуль asyncio в Python предоставляет возможности для асинхронного программирования с использованием сопрограмм (coroutine).

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

Благодаря этим преимуществам, asyncio нашел широкое применение в таких областях, как:

  • Web-разработка
  • Python API, выполняющие сетевые вызовы
  • программирование сокетов

Данное руководство предлагает детальный и всеобъемлющий обзор возможностей asyncio.

Что такое асинхронное программирование

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

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

Асинхронное программирование в Python

За асинхронное программирование в Python отвечает модуль asyncio, а также специальные инструкции async и await.

asyncio — это стандартная библиотека Python, которая предоставляет основу для написания асинхронного кода с помощью сопрограмм (coroutine, корутин).

Корутинная функция

Корутинная функция — это функция, объявленная с использованием ключевого слова async def (как функция, только с приставкой async). При вызове такой функции возвращается корутина (coroutine), которая является объектом, представляющим собой выполнение асинхронной операции. Корутины позволяют писать асинхронный код, который выглядит как синхронный, благодаря возможности приостанавливать их выполнение и возобновлять его позже.

Пример корутинной функции:

async def coroutine_function():
    print("Hello from coroutine")

Корутина (сопрограмма)

Вызов корутинной функции возвращает объект корутины.

async def coroutine_function():
    print("Hello from coroutine")

coroutine = coroutine_function()

Этот объект уже может быть запущен для выполнения с использованием оператора await:

async def main():
    await coroutine

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

Event Loop (цикл событий) — ключевой механизм, который управляет выполнением асинхронных задач, координирует исполнение корутин и обеспечивает эффективное использование ресурсов процессора.

Основные задачи Event Loop:

  • Управление задачами: Event Loop управляет запуском и выполнением задач (coroutine и futures), которые были добавлены в его очередь на выполнение. Он следит за тем, чтобы задачи, готовые к выполнению, выполнялись в правильном порядке.
  • Планирование задач: Когда корутина вызывает await на асинхронной операции (например, asyncio.sleep или асинхронный запрос к сети), Event Loop приостанавливает выполнение этой корутины и переключается на выполнение других задач. По завершению асинхронной операции, Event Loop возобновляет выполнение корутины.
  • Обработка событий: Event Loop обрабатывает события, которые могут происходить в процессе выполнения асинхронного кода, такие как завершение асинхронных операций или приход данных по сети. Он уведомляет ожидающие задачи о наступлении этих событий.
  • Организация параллельного выполнения: Event Loop позволяет асинхронным операциям выполняться параллельно, используя только один поток выполнения. Это достигается благодаря возможности Event Loop переключаться между задачами, ожидающими завершения асинхронных операций.

В простейшем случае Event Loop создаётся с помощью функции async.run.

Пример простейшей асинхронной программы на Python

import asyncio

async def main():
    print("Я собираюсь заснуть")
    await asyncio.sleep(1)
    print("Я проснулась")

asyncio.run(main())

В терминах элементов асинхронного программирования в Python, async def main(): - объявление корутинной функции, asyncio.run - создаёт Event Loop, и запускает корутину main().

Что же произойдёт при выполнении данного кода?

Event Loop запустит корутину main(), внутри неё выполнится print("Я собираюсь заснуть").

Затем запустится другая корутина, asyncio.sleep(1) (это корутина из стандартной библиотеки, и была написана за нас кем-то другим). Пока выполняется asyncio.sleep(1), Event Loop может запустить какую-нибудь другое задание, однако кроме main() у него ничего нет. Поэтому код "спит" 1 секунду, после чего выполняется print("Я проснулась").

Задача (Task)

Задача - объект, который управляет выполнением корутины в цикле событий. Задачи позволяют планировать её выполнение и отслеживать состояние. С помощью задач можно запускать несколько корутин конкурентно и управлять их выполнением.

Задачу можно создать с помощью asyncio.create_task.

Давайте рассмотрим пример:

import asyncio

async def coro1():
    print("coro1 начинает выполнение")
    await asyncio.sleep(2)
    print("coro1 завершает выполнение")

async def coro2():
    print("coro2 начинает выполнение")
    await asyncio.sleep(1)
    print("coro2 завершает выполнение")

async def main():
    print("Старт основной сопрограммы")
    task1 = asyncio.create_task(coro1())
    task2 = asyncio.create_task(coro2())

    result_1 = await task1
    result_2 = await task2

    print("Основная сопрограмма завершена")

asyncio.run(main())

task1 = asyncio.create_task(coro1()) создаёт задачу из coro1() и сразу же запускает её выполнение, не блокируя работу программы.

Таким образом, task1 и task2 будут выполняться независимо друг от друга, а также независимо от корутины main().

То есть мы можем поместить между create_task и await task1 ещё какой-либо код, и он выполнится вместе с выполнением задач. (главное условие - код должен быть до await task1; последующий код будет ожидать, что естественно, завершения соответствующей задачи).