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, возобновлялись бы.