8.9. ThreadSafeFlag¶
asyncio.ThreadSafeFlag – это примитив сигнализации, который asyncio предоставляет для случая, не поддерживаемого Event: обработчику прерывания нужно разбудить задачу asyncio. Прерывание срабатывает вне обычного планирования цикла событий, поэтому учёт, который выполняет asyncio.Event.set(), нельзя безопасно выполнить изнутри обработчика.
8.9.1. Зачем нужен отдельный примитив¶
asyncio.Event.set() выполняет учёт, который пробуждает ожидающие сопрограммы, в предположении, что он вызывается изнутри задачи, которую цикл в данный момент выполняет. Обработчик прерывания не удовлетворяет этому предположению – он может сработать между любыми двумя инструкциями и повредить то, что цикл выполнял в этот момент.
asyncio.ThreadSafeFlag.set() написан так, чтобы быть безопасным в таких контекстах. Платой за эту безопасность является меньший набор возможностей, чем у Event: ThreadSafeFlag может ожидаться только одной задачей за раз, а флаг автоматически сбрасывается, когда ожидающая задача просыпается.
8.9.2. Базовая форма¶
Типичное применение – прерывание GPIO, передающее событие нажатия кнопки задаче asyncio:
import asyncio
from machine import Pin
flag = asyncio.ThreadSafeFlag()
def on_press(pin):
flag.set()
async def watcher():
button = Pin("P1", Pin.IN, Pin.PULL_UP)
button.irq(handler=on_press, trigger=Pin.IRQ_FALLING)
while True:
await flag.wait()
print("button pressed")
asyncio.run(watcher())
on_press выполняется в контексте прерывания всякий раз, когда срабатывает GPIO; всё, что он делает, – вызывает flag.set(). Задача asyncio watcher в цикле выполняет await flag.wait(); каждый раз, когда флаг установлен, задача возобновляется, выполняет ту работу, которая требуется при нажатии кнопки, затем снова возвращается к ожиданию.
8.9.3. Три метода¶
set()– помечает флаг как установленный. Если задача в данный момент ожидает вwait(), планирует её возобновление. Безопасно вызывать из обработчика прерывания.wait()– сопрограмма. Блокируется, пока флаг не установлен, затем возвращается. Флаг автоматически сбрасывается при возврате – следующий вызовwaitснова заблокируется до следующегоset().clear()– явно сбрасывает флаг без ожидания на нём. Полезно перед первымwait, чтобы отбросить любой ложный раннийset(), сработавший до того, как задача была готова.
8.9.4. Только один ожидающий¶
Только одна задача может находиться в wait() за раз. Форма, которая нужна приложению, – это единственная задача-владелец флага – обычно та же задача, которая установила обработчик прерывания – а другие задачи общаются с ней через Event, Lock или общие структуры данных, защищённые этими примитивами.
Когда кнопка должна разбудить несколько задач, задача-владелец – это место для разветвления: приведённый выше watcher мог бы вызывать some_event.set() после каждого нажатия, и все задачи, ожидающие на some_event, возобновлялись бы.