resourcewarning 在直接关闭事件循环时静默出现,因 loop.close() 会丢弃未完成的 task/future,导致文件、socket 等资源未释放;手动管理需按 stop→等待待决任务→close→shutdown_asyncgens 顺序执行。

直接关掉事件循环前不清理待决任务,ResourceWarning 就会静默出现——不是报错,但说明有协程被丢弃、资源没释放,尤其在测试或短生命周期脚本中高频复现。
为什么 loop.close() 会触发 ResourceWarning?
loop.close() 本身不报错,但它会直接丢弃所有 pending 的 asyncio.Task 和 asyncio.Future,而这些对象若持有文件句柄、网络连接或内存引用,就可能触发 ResourceWarning。Python 开发模式(-X dev)下尤其敏感。
- 常见现象:运行完
asyncio.run(main())后没报错,但终端刷出ResourceWarning: unclosed file或unclosed socket - 根本原因:协程里开了文件(如
aiofiles.open)、用了httpx.AsyncClient、或启动了后台asyncio.create_task(),但没等它们结束就关了 loop - 注意:
asyncio.run()内部已做基础清理,但只覆盖顶层协程;手动创建的 loop(如asyncio.new_event_loop())必须自己兜底
正确关闭自定义事件循环的三步顺序
手动管理 loop 时,stop() → wait_for_pending_tasks() → close() 缺一不可。跳过中间一步,ResourceWarning 几乎必现。
- 先调用
loop.stop():让 loop 停止接收新任务,但不中断正在运行的回调 - 再显式取消并等待所有待决任务:
asyncio.all_tasks(loop)获取全部 task,对每个调用task.cancel(),然后loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True)) - 最后调用
loop.close():此时 pending 队列为空,不会丢弃任何 task - 别漏掉异步生成器:
loop.run_until_complete(loop.shutdown_asyncgens())(Python 3.6+),否则可能漏关async def里的yield资源
用 asyncio.run() 时为什么还有 ResourceWarning?
asyncio.run() 确实封装了 cleanup,但它只等待你传入的主协程返回,不负责你内部 spawn 的“火种任务”(fire-and-forget task)。这类任务最容易成为 ResourceWarning 来源。
- 典型错误写法:
asyncio.create_task(fetch_data())后没await,主协程就结束了 - 修复方式:要么改用
asyncio.create_task(...).add_done_callback(...)显式跟踪,要么把任务存到变量里,主协程退出前await asyncio.gather(*pending_tasks) - 开发阶段可加调试开关:
asyncio.run(main(), debug=True)或设置环境变量PYTHONASYNCIODEBUG=1,它会警告“coroutine was never awaited” - 测试时启用
-X dev -X tracemalloc=5,能精准定位哪行代码打开了未关闭的资源
Flask / pytest 测试中 send_from_directory 触发的 ResourceWarning 怎么办?
这不是 asyncio 专属问题,但常混在一起出现:Flask 的 send_from_directory 返回的是 Response 对象,底层用 open() 读文件,若测试框架没走完整响应生命周期,文件句柄就可能泄漏。
- 最稳妥解法:在测试里用
with app.test_request_context():包裹请求,确保上下文退出时自动 cleanup - 快速压制(仅限测试):
import warnings; warnings.filterwarnings("ignore", category=ResourceWarning),但别在生产代码里这么干 - 避免
contextlib.suppress(ResourceWarning):它只是吞掉警告,不解决资源泄漏本质 - 检查是否用了
pytest-asyncio插件但没配event_loopfixture——默认 event loop 生命周期和测试函数不一致,容易导致 loop 提前 close
真正麻烦的从来不是关 loop 这一行代码,而是你不知道哪些协程还在跑、哪些文件还开着、哪些 HTTP 连接池没回收。开发模式 + tracemalloc + debug=True 是唯一能看清真相的组合,别省这三步。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











