Python asyncio切换事件循环报错‘Event loop is closed’求助
兄弟我太懂这种被asyncio的循环问题逼疯的感觉了😅,从你给出的报错信息和代码来看,问题的根源其实和你频繁创建销毁事件循环,再加上第三方库(ccxt)的资源清理逻辑有关,咱们一步步拆解:
问题核心分析
从报错栈能看到,是ccxt的Exchange.__del__方法在尝试调用self.asyncio_loop.run_until_complete(self.session.close())时,发现事件循环已经被关闭了。为什么会概率性出现?因为Python的垃圾回收(GC)时机不确定:有时候Exchange对象在你关闭循环前就被回收了,没问题;但更多时候它会留在内存里,等你关闭旧循环、创建新循环之后才被GC,这时候它持有的旧循环引用已经失效,就触发了错误。
而你原来的写法里,每次循环都创建、关闭事件循环是完全没必要的——asyncio的事件循环设计就是用来复用的,频繁销毁重建反而会导致异步资源的生命周期和循环绑定出问题。
具体解决方案
1. 复用事件循环,不要每次循环都重建
把事件循环的创建和关闭逻辑移到整个targetData循环的外面,用一个事件循环处理所有异步任务,这是asyncio的标准用法:
import asyncio # 把单条数据的处理逻辑封装成协程 async def process_single_data(data): # 异步加载graph graph = await load_graph(data) paths = bellman_ford(graph, unique_paths=True) for path in paths: try: print_opportunity_for_path(graph, path) except Exception as exc: print(f'Error: {exc}') # 如果需要等待1秒,用asyncio.sleep(这才是正确的异步等待方式) await asyncio.sleep(1) # 主协程,遍历所有数据 async def main(): for data in targetData: await process_single_data(data) if __name__ == "__main__": loop = asyncio.get_event_loop() try: # 用一个循环跑完所有任务 loop.run_until_complete(main()) finally: # 所有任务完成后再关闭循环 loop.close()
2. 显式清理ccxt的异步资源
ccxt的异步Exchange对象依赖事件循环来关闭内部的ClientSession,不要依赖它的__del__方法自动清理(因为__del__的执行时机不可控),要在使用完后显式调用await exchange.close():
比如在你的load_graph函数里,应该这样处理:
async def load_graph(data): # 创建ccxt异步实例 exchange = ccxt.async.gdax() try: # 这里写你的业务逻辑,比如获取交易对数据、构建graph # ... return graph finally: # 显式关闭exchange,确保session在循环关闭前就清理完毕 await exchange.close()
3. 修正错误的异步等待写法
你原来代码里的asyncio.wait(1)是错误用法:asyncio.wait是用来等待一组协程完成的API,要实现“等待1秒”的效果,应该用await asyncio.sleep(1),而且必须放在协程函数内部执行。
为什么原来的写法会出问题?
- 频繁创建销毁事件循环,打破了异步资源(比如ccxt的session、连接)和循环的生命周期绑定;
- 依赖第三方库的
__del__方法清理资源,但__del__的执行时机由GC决定,大概率会在你关闭旧循环之后才触发,导致它尝试用已关闭的循环执行异步操作; - 错误使用
asyncio.wait,没有正确实现异步等待逻辑。
按照上面的方案修改后,应该就能彻底解决这个概率性报错的问题了!
内容的提问来源于stack exchange,提问作者Isaiah Y

