asyncio如何同时运行两个异步函数:象棋引擎与websockets服务并行
问题根因
- 现有启动逻辑的本质是给两个异步服务各写了一个独占事件循环的启动入口,完全没用到asyncio的并行调度能力
- 执行
run_forever()启动websockets时,事件循环被这个调用彻底占死,后续编写的asyncio.run(main())永远没有执行机会,象棋引擎自然不会被触发 - 注释掉
run_forever()逻辑后,循环只运行象棋引擎的主流程,websockets服务从未被注册为循环内的可调度任务,自然无法监听端口提供服务
- 执行
- asyncio的并行实现核心是在单个事件循环内注册多个独立的常驻协程,靠调度器的协程切换实现并发,从来不是靠多次启动独立循环来完成并行运行的
调整后的实现逻辑
- 先改造两个服务的协程结构,删掉所有独立启动循环的冗余代码,让两个服务都成为可被主循环调度的普通协程:
- 将
start_engine()整理为独立的常驻协程,内部封装和象棋引擎的连接、分析轮询逻辑,不要在这个函数内部直接调用事件循环的启动方法 - 把websockets的服务初始化逻辑整合进主流程,不要在请求处理函数或者服务初始化环节单独嵌入
run_forever()逻辑
- 将
- 编写统一的主入口协程,将两个服务并发拉起,核心结构参考:
async def main(): # 绑定所有可被局域网访问的网卡,指定websocket服务端口 async with websockets.serve(hello, "0.0.0.0", 8765): # 将象棋引擎逻辑注册为后台常驻任务 engine_task = asyncio.create_task(start_engine()) # 并发等待两个任务永久运行,不主动退出 await asyncio.gather( engine_task, # 构造永不完成的Future,保持主协程常驻 asyncio.get_running_loop().create_future() ) # 全流程仅保留这一个启动入口 if __name__ == "__main__": asyncio.run(main()) - 两个服务间的通信用异步队列解耦:websockets收到的客户端请求直接丢进全局
asyncio.Queue,象棋引擎的分析循环从队列中取任务处理,处理完成的结果再通过队列推回对应的websocket连接,避免跨任务直接调用导致的上下文错乱
常见踩坑提醒
- 不要手动调用
asyncio.get_event_loop()获取旧版循环,Python3.7+之后asyncio.run()会自动完成循环的创建、绑定、资源清理,手动获取循环很容易出现协程和循环不绑定的报错 - 如果象棋引擎的调用是阻塞型IO(比如直接读取UCI引擎的stdout输出),一定要把阻塞操作包裹在
asyncio.to_thread()中,不然会卡死整个事件循环,导致websockets服务无法响应请求 - 不要用多线程分别启动两个服务的事件循环,跨线程操作异步对象会直接抛出上下文不匹配的异常,对这两个轻量常驻服务来说,单循环调度的性能完全足够
- 给两个常驻任务增加异常捕获逻辑,避免单任务崩溃导致整个进程直接退出
内容的提问来源于stack exchange,提问作者BBedit
相关产品推荐
相关产品推荐

