关于Python 异步编程中最常用的通信机制asyncio.Event 一文详解释。
asyncio.Event 是 Python 异步编程中最简单、最常用的通信机制。
示例代码:
async def wrap_done(fn: Awaitable, event: asyncio.Event):
"""Wrap an awaitable with a event to signal when it's done or an exception is raised."""
try:
await fn
except Exception as e:
logging.exception(e)
msg = f"Caught exception: {e}"
logger.error(f'{e.__class__.__name__}: {msg}', exc_info=e if log_verbose else None)
finally:
# Signal the aiter to stop.
event.set()
你可以把它想象成一个 “红绿灯” 或者 “发令枪”。
- 默认状态(红灯):路不通,所有人都得停下来等(
await event.wait())。 event.set()(变绿灯):路通了!所有正在等红灯的人,瞬间被唤醒,继续干活。event.clear()(变红灯):又把路封死了,后面来的人又得等。
下面我通过 三个最经典的实际场景 来让你彻底明白。
场景一:起跑发令枪(协调依赖关系)
场景描述:你写了一个爬虫,分为“启动数据库”和“开始抓取”两步。
必须先等数据库连接成功了,再开始抓取。如果数据库没好,爬虫就得死等。
import asyncio
async def start_database(event: asyncio.Event):
print("💾 数据库:正在启动中...")
await asyncio.sleep(2) # 模拟连接耗时
print("💾 数据库:启动成功!连接已建立。")
# 【关键点】:按下开关,发出信号!
event.set()
async def run_spider(event: asyncio.Event):
print("🕷️ 爬虫:准备就绪,等待数据库信号...")
# 【关键点】:死等!直到有人调用 set(),否则程序会一直卡在这里,不消耗 CPU
await event.wait()
print("🕷️ 爬虫:收到信号!开始疯狂抓取数据...")
async def main():
# 1. 创建一个事件对象(默认为红灯)
event = asyncio.Event()
# 2. 同时启动两个任务
# 注意:虽然是同时启动,但爬虫会被 event.wait() 卡住
task1 = asyncio.create_task(start_database(event))
task2 = asyncio.create_task(run_spider(event))
await task1
await task2
asyncio.run(main())
运行结果:
🕷️ 爬虫:准备就绪,等待数据库信号...
💾 数据库:正在启动中...
(等待2秒...)
💾 数据库:启动成功!连接已建立。
🕷️ 爬虫:收到信号!开始疯狂抓取数据... <-- 只有数据库好了,它才会动
场景二:终止信号(优雅停止后台任务)
场景描述:你有一个后台任务一直在心跳检测(无限循环)。你想在主程序退出时,通知这个后台任务“别跳了,收工回家”。
这正是 event.is_set() 的用武之地。
import asyncio
async def background_heartbeat(stop_event: asyncio.Event):
print("💓 心跳任务:开始运行...")
# 【关键点】:每次循环都检查一下,开关是不是被按下了?
# 如果没被按下 (not set),就一直跑
while not stop_event.is_set():
print("💓 咚-咚-咚")
# 这里用 wait 设置超时,相当于 sleep,但如果在 sleep 期间 event 变了,能立马醒来
try:
# 等待 1 秒,或者直到 event 被 set
await asyncio.wait_for(stop_event.wait(), timeout=1.0)
except asyncio.TimeoutError:
continue # 超时意味着没收到停止信号,继续下一次心跳
print("💓 心跳任务:收到停止指令,优雅退出!")
async def main():
stop_event = asyncio.Event()
# 启动后台任务
task = asyncio.create_task(background_heartbeat(stop_event))
# 主程序做点别的事,比如睡 3 秒
await asyncio.sleep(3)
print("🛑 主程序:时间到了,该关机了!")
# 【关键点】:按下开关,通知后台任务停止
stop_event.set()
await task
asyncio.run(main())
场景三:你的代码中的用法(任务监视器)
回到你之前问的 wrap_done 代码。那个场景属于 “生产者-消费者”的变体。
场景:
- 生产者(Task):负责去 OpenAI 拿数据(可能要很久,也可能报错)。
- 消费者(Iterator):负责把拿到的数据吐给前端。
问题:消费者怎么知道生产者是不是干完了(或者挂了)?
import asyncio
# 模拟那个 wrap_done
async def producer_task(event: asyncio.Event):
try:
print("🏭 生产者:开始干活...")
await asyncio.sleep(2) # 模拟干活
print("🏭 生产者:活干完了!")
except Exception:
print("🏭 生产者:出错了!")
finally:
print("🏭 生产者:按下结束开关 (Event Set)")
# 【关键】:无论成功还是失败,必须通知等待者
event.set()
async def consumer_iterator(event: asyncio.Event):
print("😋 消费者:我在等结果...")
# 这是一个简化的逻辑,实际场景中通常配合 Queue 使用
# 这里消费者在等生产者那个 finally 里的信号
await event.wait()
print("😋 消费者:看到开关亮了,我知道生产者已经结束了(或者挂了)。")
print("😋 消费者:我可以放心地关闭连接了。")
async def main():
event = asyncio.Event()
await asyncio.gather(producer_task(event), consumer_iterator(event))
asyncio.run(main())
总结 event.set()
- 动作:它就是把那个不可见的开关,从 OFF (False) 拨到了 ON (True)。
- 后果:所有因为调用了
await event.wait()而卡在那里排队的代码,全部瞬间解除冻结,继续向下运行。 - 你的代码意图:在
wrap_done里,event.set()的意思是 —— “嘿,外面的兄弟,我这个异步任务彻底结束了(不管死活),你可以根据这个信号去做清理工作(比如断开 HTTP 连接)了。”

浙公网安备 33010602011771号