【python】RL学习asyncio.Event()
asyncio.Event() 是 asyncio 库中的一个同步原语,用于在协程之间进行通信和协调。下面是它的主要用法:
基本概念
创建事件
import asyncio
event = asyncio.Event()
主要方法
wait()- 等待事件被设置
async def waiter(event):
print("等待事件...")
await event.wait() # 阻塞直到事件被设置
print("事件已触发!")
set()- 设置事件(唤醒所有等待的协程)
async def setter(event):
await asyncio.sleep(2)
print("设置事件")
event.set() # 唤醒所有在等待的协程
clear()- 清除事件(重置为未设置状态)
event.clear() # 重置事件,后续的 wait() 会再次阻塞
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)
注意事项
-
set()会唤醒所有等待的协程- 事件被设置后,所有
wait()都会立即返回 - 使用
clear()后才能再次阻塞
- 事件被设置后,所有
-
事件是一次性的吗?
- 不是,
set()后需要手动clear()才能重新等待 - 可以使用
asyncio.Condition或asyncio.Semaphore实现不同模式
- 不是,
-
线程安全
asyncio.Event不是线程安全的- 如果需要在多线程中使用,考虑使用
threading.Event
-
与
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 的基本用法和常见模式。它是一个简单但强大的工具,适合在异步编程中进行协程间的协调和通信。
更多推荐



所有评论(0)