2.32. Сопрограммы

Сопрограмма (coroutine) – это функция, которая может приостановиться на полпути и позже возобновить работу с того места, где остановилась, сохранив все свои локальные переменные. Она повторяет шаблон генератора – тело функции, которое можно приостановить и возобновить, – с одним отличием в том, кто управляет каждым возобновлением.

Синтаксис Python для написания сопрограммы – это пара ключевых слов async / await. async помечает функцию как сопрограмму; await отмечает внутри неё точки, в которых разрешена приостановка.

2.32.1. Определение сопрограммы

Функция, определённая с помощью async def, является функцией-сопрограммой. Её вызов не запускает тело; он возвращает объект-сопрограмму, который ещё не начал выполняться:

async def greet(name):
    print("hello,", name)

coro = greet("Alice")        # body NOT run yet

Объект-сопрограмма приостановлен в самом начале функции. Что-то должно управлять им, чтобы тело начало выполняться – этим чем-то является цикл событий, компонент среды выполнения. MicroPython поставляет такой цикл в модуле asyncio. Пока что воспринимайте сопрограмму как «готовую к запуску, ожидающую драйвера».

2.32.2. Приостановка внутри сопрограммы

Внутри сопрограммы выражение await приостанавливает выполнение до тех пор, пока ожидаемое значение не будет готово:

async def fetch_and_log():
    data = await read_sensor()
    print("got:", data)

Когда тело достигает await read_sensor(), сопрограмма передаёт управление тому, что её выполняет. Когда read_sensor() завершается, драйвер возобновляет сопрограмму со следующей строки, привязав результат к data.

await допустим только внутри сопрограммы. Его использование в обычной функции является синтаксической ошибкой.

2.32.3. Связь с генераторами

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

  • Генератор выдаёт значения; потребитель извлекает следующее с помощью next() или путём итерации.

  • Сопрограмма уступает управление; цикл событий планирует возобновление, когда ожидаемая операция готова.

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

Цикл событий (event loop) – это небольшой диспетчер, который ведёт список сопрограмм, ожидающих чего-либо (таймера, сетевого события, завершения другой сопрограммы). На каждой итерации он выбирает сопрограмму, ожидание которой удовлетворено, возобновляет её до следующего await, затем фиксирует, чего теперь ждёт эта сопрограмма, и переходит к другой готовой. В результате множество задач продвигается одновременно в одном потоке – каждая сопрограмма добровольно уступает управление в своих точках await, а цикл заполняет эти моменты любыми другими сопрограммами, готовыми продвинуться.

Под капотом await и yield используют одну и ту же возможность среды выполнения Python для приостановки и возобновления функции. Ключевые слова различаются, потому что различаются связанные с ними соглашения: yield передаёт значение обратно потребителю, извлекающему его с помощью next(); await передаёт управление циклу событий, который планирует возобновление, когда ожидаемая операция готова. async / await – по сути более новый синтаксис для шаблона сопрограммы; старые библиотеки строили сопрограммы непосредственно поверх механизма генераторов, используя yield from (представленный в Итераторы и генераторы) для делегирования приостановки между сопрограммами.

2.32.4. Сопрограммам нужен драйвер

Сопрограмма инертна без среды выполнения, которая ею управляет. Определить её – это нормально; для запуска нужен цикл событий. Модуль asyncio в MicroPython предоставляет этот цикл событий. Раздел Asyncio описывает, как запустить цикл, планировать в нём сопрограммы, разделять состояние между ними с помощью блокировок и событий, обрабатывать отмену и тайм-ауты, а также выстраивать реальное приложение вокруг представленных здесь ключевых слов async / await.