关于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()

  1. 动作:它就是把那个不可见的开关,从 OFF (False) 拨到了 ON (True)
  2. 后果:所有因为调用了 await event.wait() 而卡在那里排队的代码,全部瞬间解除冻结,继续向下运行。
  3. 你的代码意图:在 wrap_done 里,event.set() 的意思是 —— “嘿,外面的兄弟,我这个异步任务彻底结束了(不管死活),你可以根据这个信号去做清理工作(比如断开 HTTP 连接)了。”
posted @ 2025-12-13 15:40  AlphaGeek  阅读(286)  评论(0)    收藏  举报