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 (рассматривается далее).