11.9. ThreadSafeFlag¶
asyncio.ThreadSafeFlag is the signalling primitive
asyncio provides for the case Event does
not support: an interrupt handler needs to wake an
asyncio task up. The interrupt fires outside the event
loop’s normal scheduling, so the bookkeeping
asyncio.Event.set() does cannot be done safely from
inside it.
11.9.1. Why a separate primitive¶
asyncio.Event.set() runs the bookkeeping that wakes
waiting coroutines under the assumption that it is being
called from inside a task the loop is currently running. An
interrupt handler does not satisfy that assumption – it can
fire between any two instructions and corrupt whatever the
loop was halfway through doing.
asyncio.ThreadSafeFlag.set() is written to be safe in
those contexts. The trade-off for that safety is a smaller
feature set than Event: a
ThreadSafeFlag can only be waited on by
one task at a time, and the flag auto-resets when the
waiting task wakes up.
11.9.2. The basic shape¶
A typical use is a GPIO interrupt handing off a button-press event to an asyncio task:
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 runs in interrupt context whenever the GPIO
fires; all it does is call flag.set(). The asyncio task
watcher awaits flag.wait() in a loop; each time
the flag is set, the task resumes, runs whatever work the
button press requires, then loops back to wait again.
11.9.3. The three methods¶
set()– mark the flag as set. If a task is currently waiting inwait(), schedule it to resume. Safe to call from an interrupt handler.wait()– a coroutine. Block until the flag is set, then return. The flag is automatically cleared on return – the next call towaitwill block again until the nextset().clear()– explicitly clear the flag without waiting on it. Useful before a firstwaitto discard any spurious earlyset()that fired before the task was ready.
11.9.4. One waiter only¶
Only one task may be in wait()
at a time. The shape an application wants is a single
owner task for the flag – usually the same task that
installed the interrupt handler – with other tasks
communicating with it through Event,
Lock, or shared data structures protected
by those primitives.
When a button has to wake several tasks, the owner task is
the place to fan out: the watcher above could call
some_event.set() after each press, and all the tasks
waiting on some_event would resume.