直接关闭 aiohttp.ClientSession 不等于关闭底层连接池,因为 session.close() 仅标记会话不可用,真正管理连接的是 TCPConnector;若不显式 await connector.close(),可能残留 socket 并触发 ResourceWarning 或 RuntimeError。

为什么直接关闭 aiohttp.ClientSession 不等于关闭底层连接池
很多人以为调用 session.close() 就万事大吉,其实它只是标记会话为“不可再用”,并不立即释放 TCP 连接。真正管理空闲连接、复用和清理的是底层的 aiohttp.TCPConnector。如果没显式等待连接器完成清理,程序退出时可能残留未关闭的 socket,尤其在短生命周期脚本或测试中容易触发 ResourceWarning: unclosed transport 或 RuntimeError: Event loop is closed。
-
ClientSession关闭后,connector仍可能在后台尝试关闭空闲连接(比如设置了keepalive_timeout) - 若事件循环已关闭(如
asyncio.run()结束后),connector 的清理协程会直接抛RuntimeError - 默认 connector 的
limit是 100,但limit_per_host是 0(不限制),高并发下不主动管理容易堆积连接
正确关闭 connector 的三步实操顺序
必须按「先关 session → 等 connector 清理完成 → 确保事件循环未关闭」的顺序来。最稳妥的方式是显式获取 connector 并 await 其 close() 方法:
import asyncio
import aiohttp
<p>async def main():
connector = aiohttp.TCPConnector(
limit=30,
limit_per_host=5,
keepalive_timeout=15,
)
async with aiohttp.ClientSession(connector=connector) as session:
async with session.get("<a href="https://www.php.cn/link/4d2fe2e8601f7a8018594d98f28706f2">https://www.php.cn/link/4d2fe2e8601f7a8018594d98f28706f2</a>") as resp:
await resp.text()</p><pre class="brush:php;toolbar:false;"># ✅ 关键:await connector.close(),不能只靠 session.close()
await connector.close()asyncio.run(main())
- 不要用
session.connector.close()—— 它返回的是协程对象,不 await 就没效果 - 如果 session 是用默认 connector 创建的(即没传
connector=...),需从session.connector获取后 awaitclose() - 避免在
async with外部再手动调session.close(),会报RuntimeError: Session is closed
测试/命令行脚本中容易漏掉的清理点
在非 web 服务类场景(比如 CLI 工具、数据采集脚本),常忽略 connector 的生命周期与事件循环绑定关系。常见错误是:
- 用
asyncio.get_event_loop().run_until_complete(...)后未显式loop.close(),导致 connector 的清理任务被丢弃 - 在信号处理(如
SIGINT)中只session.close(),没 await connector 清理,Ctrl+C 后仍报 warning - 单元测试里多个
ClientSession实例共用一个 connector(比如全局 singleton),关闭一个 session 不代表 connector 可安全关闭
推荐做法:对短任务,统一用 asyncio.run() + 显式 await connector.close();对长任务或需响应信号的,用 asyncio.create_task() 启动清理,并在 finally 块中 await。
connector 参数对关闭行为的实际影响
keepalive_timeout 和 force_close 直接决定关闭时是否要等空闲连接超时:
-
keepalive_timeout=0:禁用长连接,每次请求后立即关闭 socket,connector.close()几乎瞬时返回 -
force_close=True:跳过等待空闲连接,强制关闭所有 transport —— 适合调试或确定无活跃请求时快速退出 -
enable_cleanup_closed=True(默认 False):让 connector 主动清理已关闭但未释放的 transport,减少ResourceWarning,但会略微增加关闭耗时
生产环境建议保留 keepalive_timeout=15 + enable_cleanup_closed=True,既复用连接又避免资源泄漏;调试阶段可开 force_close=True 快速验证逻辑。
connector 的关闭不是“一劳永逸”的操作,它依赖事件循环存活、没有 pending 请求、且自身未被其他 session 共享。最容易被忽略的是:多个 ClientSession 实例如果共享同一个 TCPConnector,那只有最后一个 session 关闭后,才应调 await connector.close() —— 否则提前关会导致后续 session 报 ConnectionClosedError。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











