8.7. 事件

asyncio.Event 是該模組提供的最簡單的訊號原語。一個協程在某件事發生時設定一個事件;任意數量的其他協程則等待該事件被設定後才繼續。它沒有酬載 -- 事件就是一個布林值,會翻轉為開啟並保持開啟,直到有東西清除它為止。

8.7.1. 基本形態

import asyncio

async def waiter(evt):
    print("waiting")
    await evt.wait()
    print("got signal")

async def main():
    evt = asyncio.Event()
    asyncio.create_task(waiter(evt))
    await asyncio.sleep(1)
    evt.set()
    await asyncio.sleep(0)

asyncio.run(main())

等待者任務開始執行,碰到 await evt.wait() 後被暫停 -- 事件一開始處於已清除狀態,所以 wait 呼叫會將控制權交還給迴圈。一秒後 main 呼叫 evt.set(),等待者便被排程恢復執行。結尾的 await asyncio.sleep(0) 只是一次讓出,好讓迴圈在 main 回傳之前有機會執行等待者。

8.7.2. 四個方法

  • set() -- 將事件翻轉為已設定,並排程目前所有阻塞在 wait() 上的協程恢復執行。事件會保持已設定,直到被清除為止。

  • clear() -- 將事件翻轉回已清除。之後 await wait() 的協程會再次阻塞。

  • wait() -- 一個協程。若事件已被設定,則立即回傳。否則阻塞,直到有東西設定它為止。

  • is_set() -- 不阻塞地回傳目前狀態。當協程想檢查事件而不等待它時很有用。

8.7.3. 多個等待者

數個協程可以 await 同一個事件。當有東西呼叫 set() 時,它們全部都會被排程恢復執行。事件預設是一對多的;不需要用數個事件來扇出到數個等待者。

8.7.4. 重複使用事件

一種常見的模式是反覆使用同一個事件 -- 每收到一個訊號就執行一次的協程 -- 做法是在每一輪之後清除它:

async def worker(go):
    while True:
        await go.wait()
        do_one_unit_of_work()
        go.clear()

生產者每當有工作時就設定事件;工作者一旦接收到訊號就清除它。如果生產者可能在工作者還沒醒來之前再次設定事件,工作者可能會想在清除之前處理那種情況(while go.is_set(): ...)。

8.7.5. 安全的情境

從中斷處理器內部對 Event 呼叫 set()安全的。它用來排程等待者的機制,假定自己是在事件迴圈本身的情境中執行,而中斷並非如此。若要從中斷處理器喚醒一個 asyncio 任務,ThreadSafeFlag(即將介紹)才是正確的原語。