Python外部定义事件循环时如何自动关闭aiohttp ClientSession
问题原因分析
asyncio.run() 的执行逻辑是「创建全新事件循环→运行传入的协程→运行结束后彻底关闭并销毁事件循环」,而 atexit 注册的回调是在进程退出前才执行,此时 asyncio.run() 创建的事件循环已经被完全销毁,所以 close 方法中调用 loop.run_until_complete 自然会抛出 Event loop is closed 错误。
另外补充:你的代码中给 ClientSession 传 loop 参数是 aiohttp 2.x 版本的老写法,3.0+ 版本已经不需要手动传 loop,会自动绑定当前运行的事件循环,手动传反而容易出现循环不匹配的问题。
问题1解答:外部创建事件循环时实现ClientSession自动关闭的方案
- 方案1:使用异步上下文管理器(最推荐,符合异步编程规范)
异步资源的自动管理标准方案是实现__aenter__和__aexit__方法,用户通过async with创建实例,退出上下文时自动触发关闭逻辑,完全不需要依赖atexit,示例代码:
# module/client.py import aiohttp class Client: def __init__(self): self._session = aiohttp.ClientSession() async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): # 退出上下文时自动关闭session await self._session.close() async def foo(self): # 业务逻辑 pass
调用侧写法:
# main.py import asyncio import module async def main(): async with module.Client() as client: await client.foo() # 退出async with块时session已经完成自动关闭 asyncio.run(main())
- 方案2:注册事件循环关闭回调(适配不想修改调用方式的场景)
如果不想用async with语法,可以直接给事件循环注册关闭前的回调,替代atexit,回调会在事件循环被销毁前执行,不会出现循环已关闭的问题:
# module/client.py import asyncio import aiohttp class Client: def __init__(self, loop=None): self.loop = loop or asyncio.get_running_loop() self._session = aiohttp.ClientSession() # 注册循环关闭时的自动清理任务 self.loop.create_task(self._auto_close()) async def _auto_close(self): # 等待循环内置的异步生成器销毁完成后再关session await self.loop.shutdown_asyncgens() if not self._session.closed: await self._session.close() async def foo(self): # 业务逻辑 pass
问题2解答:异步公共方法内部逻辑未全部await的影响
一定会产生功能异常,常见影响包括:
- 未await的协程不会被调度执行,对应的业务逻辑直接失效,比如内部发请求的协程未await,请求根本不会发送
- 资源泄漏:未执行完的请求持有的网络连接不会被释放,session关闭时会抛出未完成任务的警告
- 异常无法捕获:未await的协程如果抛出异常,不会向上抛到调用层,只会触发事件循环的全局异常回调,容易出现静默报错,排查难度极高
- 时序混乱:未await的逻辑执行时机完全不可控,可能在后续逻辑执行完、甚至程序退出前才执行,导致数据不一致
内容的提问来源于stack exchange,提问作者duhby
相关产品推荐
相关产品推荐

