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. 三個方法

  • set() ——將旗標標記為已設定。如果有任務正在 wait() 中等待,便排程它恢復執行。可安全地從中斷處理常式呼叫。

  • wait() ——一個協程。會阻塞直到旗標被設定,然後返回。旗標在返回時會 自動清除 ——下一次呼叫 wait 將再次阻塞,直到下一次 set()

  • clear() ——在不等待旗標的情況下明確清除它。在第一次 wait 之前很有用,可丟棄任何在任務就緒之前觸發的虛假早期 set()

8.9.4. 僅限單一等待者

同一時間只能有一個任務處於 wait() 中。應用程式想要的形式是為該旗標設置一個單一的 擁有者 任務——通常就是安裝中斷處理常式的那個任務——其他任務則透過 EventLock 或由這些原語保護的共享資料結構與它溝通。

當一個按鈕必須喚醒多個任務時,擁有者任務就是進行扇出的地方:上面的 watcher 可以在每次按下後呼叫 some_event.set(),而所有等待 some_event 的任務便會恢復執行。