asyncio.Event() 是 asyncio 库中的一个同步原语,用于在协程之间进行通信和协调。下面是它的主要用法:

基本概念

创建事件

import asyncio

event = asyncio.Event()

主要方法

  1. wait() - 等待事件被设置
async def waiter(event):
    print("等待事件...")
    await event.wait()  # 阻塞直到事件被设置
    print("事件已触发!")
  1. set() - 设置事件(唤醒所有等待的协程)
async def setter(event):
    await asyncio.sleep(2)
    print("设置事件")
    event.set()  # 唤醒所有在等待的协程
  1. clear() - 清除事件(重置为未设置状态)
event.clear()  # 重置事件,后续的 wait() 会再次阻塞
  1. is_set() - 检查事件是否已设置
if event.is_set():
    print("事件已设置")

完整示例

基本使用

import asyncio

async def waiter(event):
    print("开始等待事件")
    await event.wait()
    print("事件触发,继续执行")

async def setter(event):
    await asyncio.sleep(3)
    print("设置事件")
    event.set()

async def main():
    event = asyncio.Event()
    
    # 创建多个等待任务
    tasks = [
        asyncio.create_task(waiter(event)),
        asyncio.create_task(waiter(event)),
        asyncio.create_task(setter(event))
    ]
    
    await asyncio.gather(*tasks)

asyncio.run(main())

生产者-消费者模式

import asyncio
import random

async def producer(event, data_ready):
    for i in range(3):
        await asyncio.sleep(random.uniform(0.5, 1.5))
        print(f"生产者: 生产了数据 {i}")
        data_ready.set()  # 通知消费者数据已准备好
        event.clear()  # 重置,等待下一轮

async def consumer(event, data_ready):
    for i in range(3):
        await data_ready.wait()  # 等待数据准备好
        print(f"消费者: 消费数据 {i}")
        event.set()  # 通知生产者可以继续生产

async def main():
    event = asyncio.Event()
    data_ready = asyncio.Event()
    
    await asyncio.gather(
        producer(event, data_ready),
        consumer(event, data_ready)
    )

asyncio.run(main())

超时等待

async def waiter_with_timeout(event):
    try:
        # 最多等待2秒
        await asyncio.wait_for(event.wait(), timeout=2)
        print("事件在超时前触发")
    except asyncio.TimeoutError:
        print("等待超时")

async def main():
    event = asyncio.Event()
    
    # 这个事件永远不会被设置
    task = asyncio.create_task(waiter_with_timeout(event))
    await asyncio.sleep(3)
    await task

让我们一步步分析:

在main函数中,创建了一个asyncio.Event对象,名为event。

然后,创建了一个任务task,它执行waiter_with_timeout(event)。

接着,main函数等待(await asyncio.sleep(3))3秒钟。

最后,main函数等待任务task完成(await task)。

现在,我们来看waiter_with_timeout函数:

它尝试等待事件,但最多只等2秒(通过asyncio.wait_for设置超时)。

如果事件在2秒内被设置,那么event.wait()会立即返回(实际上,在事件被设置后,wait会返回,然后wait_for也会返回),然后打印“事件在超时前触发”。

如果事件在2秒内没有被设置,那么asyncio.wait_for会抛出asyncio.TimeoutError异常,然后我们捕获这个异常并打印“等待超时”。

在这个例子中,事件event永远不会被设置(因为main函数中没有调用event.set()),所以waiter_with_timeout中的event.wait()会一直等待。但是,我们通过asyncio.wait_for设置了2秒的超时,所以2秒后会超时,抛出TimeoutError。

然而,注意main函数中,我们在创建任务后立即等待了3秒(await asyncio.sleep(3))。这意味着,任务task有足够的时间来执行并超时(2秒超时)。所以,在main函数等待3秒后,任务task已经完成(因为超时,所以已经打印了“等待超时”)。然后,main函数执行await task,此时任务已经完成,所以立即返回。

因此,整个程序的输出将是:

text
等待超时
因为事件没有被设置,所以2秒后超时,打印“等待超时”。然后,main函数继续等待剩下的1秒(因为总共等待3秒,而任务在2秒时已经完成,所以main函数在等待3秒时,实际上在任务完成后再等待了1秒)。

让我们用时间线来更清晰地表示:

时间0:启动任务task,任务开始等待事件(最多等2秒)。

时间0:同时,main函数开始等待3秒。

时间2秒:任务task等待事件超时,打印“等待超时”,任务完成。

时间3秒:main函数的3秒等待结束,然后执行await task,但任务已经完成,所以立即继续。

注意:在asyncio中,当我们使用await asyncio.sleep(3)时,事件循环会切换到其他任务,所以任务task有机会运行。

如果我们想要在超时后取消任务,这里并没有显式取消,因为任务已经完成(超时后,waiter_with_timeout函数执行完毕,任务状态为完成)。所以,main函数中的await task只是等待一个已经完成的任务,所以不会阻塞。

如果我们想要在超时后仍然等待事件,那么就需要不同的设计。但在这个例子中,超时后waiter_with_timeout函数就结束了。

所以,这个例子演示了如何使用asyncio.wait_for来为等待事件设置超时。

实际应用场景

1. 服务启动协调

async def start_server(ready_event):
    # 初始化工作
    await asyncio.sleep(2)
    print("服务器启动完成")
    ready_event.set()  # 通知其他服务可以连接了

async def connect_to_server(ready_event):
    print("等待服务器就绪...")
    await ready_event.wait()
    print("连接服务器...")

2. 批量任务同步

async def worker(task_id, start_event):
    await start_event.wait()  # 所有worker同时开始
    print(f"Worker {task_id} 开始执行")

async def main():
    start_event = asyncio.Event()
    
    workers = [asyncio.create_task(worker(i, start_event)) 
               for i in range(5)]
    
    await asyncio.sleep(1)
    print("开始所有worker...")
    start_event.set()
    
    await asyncio.gather(*workers)

注意事项

  1. set() 会唤醒所有等待的协程

    • 事件被设置后,所有 wait() 都会立即返回
    • 使用 clear() 后才能再次阻塞
  2. 事件是一次性的吗?

    • 不是,set() 后需要手动 clear() 才能重新等待
    • 可以使用 asyncio.Conditionasyncio.Semaphore 实现不同模式
  3. 线程安全

    • asyncio.Event 不是线程安全的
    • 如果需要在多线程中使用,考虑使用 threading.Event
  4. asyncio.Condition 的区别

    # Event: 简单的标志位
    event = asyncio.Event()
    
    # Condition: 更复杂的通知机制,可以附加数据
    cond = asyncio.Condition()
    async with cond:
        await cond.wait()  # 等待通知
        cond.notify_all()  # 通知所有
    

常见问题

Q: 事件可以多次触发吗?
A: 可以,但需要调用 clear() 重置后才能再次等待

Q: 多个协程等待同一个事件,谁会先执行?
A: 所有等待的协程会被同时唤醒,执行顺序由事件循环决定

Q: 如何取消等待?
A: 使用 asyncio.wait_for(event.wait(), timeout) 或取消任务

这就是 asyncio.Event 的基本用法和常见模式。它是一个简单但强大的工具,适合在异步编程中进行协程间的协调和通信。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐