2.32. Корутини

Корутина — це функція, яка може призупинитися в середині виконання і пізніше відновитися з того місця, де зупинилася, зберігаючи всі локальні змінні. Вона відображає шаблон генератора — тіло функції, яке можна призупиняти та відновлювати, — з однією відмінністю в тому, хто ініціює кожне відновлення.

Синтаксис 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() або шляхом ітерації.

  • Корутина передає управління; цикл подій планує відновлення, коли очікувана операція готова.

Якщо механізм yield у генераторах зрозумілий, то механізм корутини — та сама ідея, тільки керована циклом подій замість циклу for.

Цикл подій — це невеликий диспетчер, який зберігає список корутин, що чекають на щось (таймер, мережеву подію, завершення іншої корутини). На кожній ітерації він вибирає корутину, чекання якої виконано, відновлює її до наступного await, потім фіксує, на що ця корутина тепер чекає, і переходить до іншої готової. В результаті багато завдань виконуються одночасно в одному потоці — кожна корутина добровільно передає управління у своїх точках await, і цикл заповнює ці моменти іншими корутинами, готовими до виконання.

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

2.32.4. Корутинам потрібен драйвер

Без середовища виконання корутина інертна. Визначити її можна; для запуску потрібен цикл подій. Модуль asyncio MicroPython надає такий цикл. Розділ Asyncio охоплює запуск циклу, планування корутин у ньому, спільний доступ до стану між ними за допомогою блокувань і подій, обробку скасування та тайм-аутів, а також побудову реального застосунку навколо ключових слів async / await, представлених тут.