clientsession 必须显式关闭,因其持有连接池、dns缓存等异步资源,gc不会调用close导致文件描述符泄漏;须用async with或显式await session.close(),且不可跨event loop复用。

ClientSession 必须显式关闭,不能靠 GC 回收
aiohttp 的 ClientSession 不是普通对象,它内部持有连接池、DNS 缓存、SSL 上下文等异步资源。Python 的垃圾回收(GC)不会主动调用其 __aexit__ 或 close(),导致 TCP 连接不释放、文件描述符泄漏、后续请求卡死或报 RuntimeError: Session is closed。
常见错误现象:脚本跑几次就报 OSError: [Errno 24] Too many open files;或者在 asyncio.run() 结束后还看到未完成的连接日志。
- 必须用
async with ClientSession() as session:包裹使用范围 - 若需跨多个协程复用 session(如全局 session),务必在程序退出前显式 await
session.close() - 不要在函数里返回未关闭的 session 给调用方——责任边界要清晰
复用 session 是必须的,但不能跨 event loop
一个 ClientSession 实例绑定到创建它的 asyncio event loop。如果在不同 loop(比如新线程里新建 loop、或 pytest 中反复启停 loop)中复用旧 session,会直接抛 RuntimeError: Session is closed 或静默失败。
使用场景:Web 服务(如 FastAPI)中常把 session 当作依赖注入;命令行工具中想避免每次请求都新建 session。
立即学习“Python免费学习笔记(深入)”;
Android开发指南中文pdf版,学习android的朋友可以参考下。应用程序基础Application Fundamentals 关键类 应用程序组件 激活组件:intent 关闭组件 manifest文件 Intent过滤器 Activity和任务 Affinity(吸引力)和新任务 加载模式 清理堆栈 启动任务 进程和线程 进程 线程 远程过程调用 线程安全方法 组件生命周期 Activity生命周期 调用父类 服务生命周期 广播接收器生命周期 进程与生命周期 用户界面User Interface
- FastAPI 等框架推荐用 lifespan hook,在 startup 时创建 session,shutdown 时 close
- 测试时别在
setUp里建 session,改用 fixture +yield+await session.close() - 绝对不要把 session 存在模块级变量里然后跨 test case 复用
timeout 和 connector 参数影响连接行为远超预期
ClientSession 的 timeout 控制的是单次请求生命周期,而 connector(如 TCPConnector)控制的是底层连接池策略。两者配合不当会导致超时逻辑混乱、连接复用失效、甚至 DNS 查询被缓存数分钟。
典型问题:设置了 timeout=5,但某个请求卡住 60 秒才报错;或并发请求量大时大量新建连接,触发服务器限流。
-
timeout应设为aiohttp.ClientTimeout(total=30, connect=5, sock_read=10),避免只设total导致 DNS 解析失败也计入超时 -
TCPConnector(limit=100, limit_per_host=30, ttl_dns_cache=300)—— 默认limit=100看似够用,但limit_per_host=0(即不限)才是旧版默认值,升级后容易突然变慢 - DNS 缓存默认 10 分钟,若后端做滚动发布,建议显式设
ttl_dns_cache=60
session 关闭后仍可能有 pending request 报错
关闭 session 并不等于立刻终止所有进行中的请求。如果在 await session.close() 前已有 await session.get(...) 发出但尚未收到响应,这些 task 仍会继续运行,并在读取响应体时抛 ClientConnectionError 或 CancelledError。
这不是 bug,而是 asyncio 的正常调度行为:close 只是拒绝新请求、清理空闲连接,不强制 cancel 正在传输的 request。
- 关键点:关闭 session 前,确保所有已发请求已完成或已被 cancel
- 安全做法是在 shutdown 逻辑里用
asyncio.shield()包裹关键请求,或用asyncio.wait_for(task, timeout=...)控制等待时间 - 别依赖 “关 session 就万事大吉”——网络 IO 的不确定性始终存在
session 生命周期最麻烦的地方不在怎么开,而在怎么确认它真关了:连接池清空、DNS 缓存失效、所有 pending task 归零。这些状态没法靠 print 看出来,得靠 lsof -i | grep python 或 asyncio debug 模式验证。









