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(稍后介绍)才是正确的原语。