asyncio.run() 会创建并显式关闭事件循环,之后 asyncio.get_event_loop() 返回已关闭的 loop,导致 RuntimeError。正确做法是每个异步入口都用 asyncio.run() 包裹,避免混用 get_event_loop()。
asyncio.get_event_loop() 会报 “Event loop is closed”因为 asyncio.run() 内部会创建新 event loop、运行协程、然后**显式关闭并丢弃该 loop**。之后再调用 asyncio.get_event_loop(),拿到的仍是那个已被关闭的 loop 实例(不是新 loop),调用 run_until_complete() 等方法就会触发 RuntimeError: Event loop is closed。
常见于写完一个 asyncio.run(main()) 后,又在 REPL 或脚本后续手动尝试:
asyncio.get_event_loop().run_until_complete(some_coro())
这几乎必错。
asyncio.run() 包一层,不要混用 get_event_loop()
asyncio.run() 是“一次性”的,不能复用;它不等价于“启动一个长期运行的 loop”asyncio.run() 之后保留或重用 loop 引用loop.create_task() 或 loop.run_in_executor() 怎么办本质还是 loop 已关闭,但错误可能更隐蔽——比如你在类的 __del__、atexit 回调、或信号处理器里试图调度任务,而此时主 loop 早已退出。
立即学习“Python免费学习笔记(深入)”;
典型场景:
atexit.register() 清理异步资源(如关闭 aiohttp.ClientSession)SIGINT 后尝试 graceful shutdown解决思路不是“重启 loop”,而是绕过 loop:
asyncio.run_coroutine_threadsafe(coro, loop) —— 但前提是 loop 还在运行且未关闭(适合后台线程中提交任务)session.connector._close(),不推荐但有时是唯一选择)await cleanup(),或用 asyncio.shield() 包住关键清理任务当从子线程访问 asyncio.get_event_loop(),Python 默认返回主线程的 loop(如果存在),但该 loop 很可能已在主线程中被 asyncio.run() 关闭。子线程不能直接复用主线程的已关闭 loop。
正确做法是:子线程中必须自己创建并管理 loop:
import asyncio<br>def worker():<br> loop = asyncio.new_event_loop()<br> asyncio.set_event_loop(loop)<br> loop.run_until_complete(my_coro())<br> loop.close()
set_event_loop() 绑定asyncio.get_event_loop() 在非主线程中获取 loop —— 它不保证返回当前线程的 loop(Python 3.12+ 默认行为已变更,但兼容性差)asyncio.new_event_loop() + set_event_loop() 显式初始化nest_asyncio 的真实适用边界nest_asyncio 的作用是给已关闭的 loop “打补丁”,让它能重新接受新任务 —— 这仅适用于 Jupyter、IPython、某些测试框架等**无法控制 loop 生命周期的嵌入环境**。
它不是通用解法:
await coro,或 pytest-asyncio 测试中多次调用 asyncio.run()
nest_asyncio.apply(),且只能调用一次;否则可能引发 loop 状态错乱真正需要关注的,不是怎么让 closed loop “复活”,而是厘清谁该创建 loop、谁该关闭它、以及任务是否真的需要在那个 loop 上跑。loop 关闭不是 bug,是设计预期;错误往往来自跨生命周期误用。