8.7. События

asyncio.Event – это простейший примитив сигнализации, который предоставляет модуль. Одна корутина устанавливает событие, когда что-то произошло; любое число других корутин ждут установки события, прежде чем продолжить. Полезной нагрузки нет – событие – это булево значение, которое переключается во включённое состояние и остаётся включённым, пока что-то его не сбросит.

8.7.1. Базовая форма

import asyncio

async def waiter(evt):
    print("waiting")
    await evt.wait()
    print("got signal")

async def main():
    evt = asyncio.Event()
    asyncio.create_task(waiter(evt))
    await asyncio.sleep(1)
    evt.set()
    await asyncio.sleep(0)

asyncio.run(main())

Ожидающая задача запускается, доходит до await evt.wait() и приостанавливается – событие начинается в сброшенном состоянии, поэтому вызов wait возвращает управление циклу. Через секунду main вызывает evt.set(), и ожидающая задача планируется к возобновлению. Завершающий await asyncio.sleep(0) – это просто уступка управления, чтобы цикл получил шанс выполнить ожидающую задачу до того, как main вернёт управление.

8.7.2. Четыре метода

  • set() – переключает событие в установленное состояние и планирует к возобновлению каждую корутину, заблокированную в данный момент на wait(). Событие остаётся установленным до сброса.

  • clear() – переключает событие обратно в сброшенное состояние. Корутины, которые await wait() после этого, снова заблокируются.

  • wait() – корутина. Если событие уже установлено, возвращает управление немедленно. Иначе блокируется, пока что-то его не установит.

  • is_set() – возвращает текущее состояние без блокировки. Полезно, когда корутина хочет проверить событие, не ожидая его.

8.7.3. Несколько ожидающих

Несколько корутин могут await одно и то же событие. Когда что-то вызывает set(), все они планируются к возобновлению. Событие по умолчанию работает по схеме «один ко многим»; нет нужды в нескольких событиях, чтобы разветвиться на несколько ожидающих.

8.7.4. Повторное использование события

Распространённый паттерн – использовать событие многократно – корутина, которая выполняется один раз на сигнал – сбрасывая его после каждого прохода:

async def worker(go):
    while True:
        await go.wait()
        do_one_unit_of_work()
        go.clear()

Производитель устанавливает событие всякий раз, когда есть работа; рабочий сбрасывает его, как только подхватил сигнал. Если производитель может снова установить событие до того, как рабочий проснулся, рабочему стоит обработать этот случай (while go.is_set(): ...) перед сбросом.

8.7.5. Безопасные контексты

На Event небезопасно вызывать set() изнутри обработчика прерывания. Механизм, который он использует для планирования ожидающих, предполагает, что он работает внутри собственного контекста цикла событий, чем прерывания не являются. Для пробуждения asyncio-задачи из обработчика прерывания правильным примитивом является ThreadSafeFlag (рассматривается далее).