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())
每当 GPIO 触发时,on_press 在中断上下文中运行;它所做的全部就是调用 flag.set()。asyncio 任务 watcher 在循环中 await flag.wait();每次标志被设置时,该任务恢复运行,执行按键所需的任何工作,然后再循环回去等待。
8.9.3. 三个方法¶
8.9.4. 仅限一个等待者¶
同一时刻只能有一个任务处于 wait() 中。应用程序所需要的写法是为该标志设置单一的所有者任务——通常就是安装中断处理程序的那个任务——而其他任务通过 Event、Lock,或由这些原语保护的共享数据结构与它通信。
当一次按键必须唤醒多个任务时,所有者任务就是进行扇出的地方:上面的 watcher 可以在每次按键后调用 some_event.set(),于是所有等待 some_event 的任务都会恢复运行。