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.