asyncio.run() 会显式关闭事件循环,此后调用 asyncio.get_event_loop() 等操作将报 RuntimeError: Event loop is closed;应避免复用已关闭 loop,改用 new_event_loop() 手动管理或确保 loop 生命周期与异步资源一致。

为什么 asyncio.run() 后再调用 loop 会报错
这个错误最常见于多次调用 asyncio.run() 之后,又试图手动获取或操作事件循环。因为 asyncio.run() 内部会创建新 loop、运行协程、然后**显式关闭 loop**——一旦关闭,它就不能再被复用或重新启动。
典型错误场景:asyncio.run(main()) 执行完后,接着写 asyncio.get_event_loop().create_task(...),就会触发 RuntimeError: Event loop is closed。
- 不要在
asyncio.run()调用后继续使用asyncio.get_event_loop() - 如果需要多次运行异步逻辑,改用
asyncio.new_event_loop()+loop.run_until_complete()手动管理(但注意线程安全) - 避免全局共享已关闭的 loop 实例;每次需要时应新建或确保它处于运行中
在多线程中误用主线程 loop 导致关闭
asyncio 的事件循环默认绑定到创建它的线程。如果你在子线程里调用 asyncio.get_event_loop(),Python 会自动创建新 loop;但如果主线程里已调用过 asyncio.run() 并退出,主线程 loop 就被关了——此时若子线程意外引用了主线程的 loop(比如通过闭包、全局变量或未隔离的模块状态),就会踩坑。
- 子线程必须自己调用
asyncio.set_event_loop(asyncio.new_event_loop()),不能依赖get_event_loop() - 避免跨线程传递 loop 对象;更安全的做法是:每个线程都用
asyncio.new_event_loop()+loop.run_until_complete() - 检查是否在 logging、atexit 或装饰器里隐式访问了已关闭的 loop(例如某些 ORM 或 SDK 初始化时偷偷调用
get_event_loop())
使用 aiohttp 或 aiomysql 时提前关闭 session
很多异步库(如 aiohttp.ClientSession、aiomysql.create_pool)内部持有对当前 loop 的引用。如果 session 在 loop 关闭后才被销毁(比如对象析构发生在 asyncio.run() 返回之后),就可能触发该错误。
立即学习“Python免费学习笔记(深入)”;
- 务必用
async with显式管理 session 生命周期:async with aiohttp.ClientSession() as session: - 避免把 session 存为模块级全局变量;它不是线程/loop 安全的
- 如果必须复用 session,确保它和 loop 绑定在同一生命周期内——比如在
asyncio.run()的主协程里创建并传入子协程,而不是在外部初始化
调试时打印 loop 状态的小技巧
光看错误信息很难判断哪个 loop 被关了、什么时候关的。可以在关键位置加一行诊断:
loop = asyncio.get_event_loop()
print(f"Loop status: running={loop.is_running()}, closed={loop.is_closed()}")
这能帮你快速定位是「调用前 loop 已关」还是「调用后立刻被关」。特别注意 asyncio.run() 返回后,loop.is_closed() 几乎总是 True。
- 不要依赖
loop.is_closed()做恢复逻辑——已关闭的 loop 无法重启 - 在单元测试中频繁启停异步代码时,建议统一用
pytest-asyncio插件管理 loop,而非手写asyncio.run() - 第三方库(如 fastapi、httpx)可能封装了自己的 loop 管理逻辑,遇到此错误先查文档确认其推荐的使用方式


















