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