8.3. Цикл событий

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

Большинство приложений никогда не взаимодействуют с циклом напрямую. Цикл является следствием вызова asyncio.run(); приложение пишет корутины, планирует их как задачи, а цикл делает остальное.

8.3.1. Что на самом деле делает asyncio.run()

Один вызов:

asyncio.run(main())

является сокращением для более длинной последовательности, которой цикл управляет от имени приложения:

  1. Создать цикл событий, если он ещё не существует.

  2. Обернуть переданную корутину в задачу и запланировать её как точку входа верхнего уровня цикла.

  3. Запустить цикл — проходить по готовым задачам, ждать, когда ни одна не готова, возобновлять задачи, когда их await завершаются, — пока задача верхнего уровня не вернётся или не возбудит исключение.

  4. Отменить все созданные приложением задачи, которые всё ещё выполняются.

  5. Вернуть то, что вернула корутина верхнего уровня (или повторно возбудить то, что она возбудила).

8.3.2. Один цикл на программу

В asyncio для MicroPython есть один цикл событий, и точка. Нельзя создать новый цикл, и нельзя вложить один цикл в другой. Вызов asyncio.run() изнутри корутины, которая уже выполняется в цикле, является ошибкой; цикл уже есть, и корутине просто нужно выполнить await над тем, что она хотела запустить.

На практике правило такое же, как заключительная строка предыдущей страницы: существует ровно один вызов asyncio.run() на программу, наверху, с единственной async def main() за ним. Всё остальное живёт внутри main.

8.3.3. Прямой доступ к циклу

Для редких случаев, когда приложению нужно обратиться к самому циклу — в основном диагностика и обработчики исключений — asyncio.get_event_loop() возвращает объект Loop. Оттуда приложение может установить пользовательский обработчик исключений, проверить, чем занят цикл, или (очень изредка) вызвать create_task() напрямую вместо asyncio.create_task() (это одна и та же операция).

Полный набор методов, которые предоставляет Looprun_forever(), stop(), set_exception_handler() и остальные — рассматривается на странице управление циклом далее в этом разделе. А до тех пор asyncio.run(main()) — это всё, что нужно приложению.