保留后台任务时如何让asyncio记录aiohttp未关闭会话错误?
问题:保留后台任务时记录aiohttp未关闭会话的错误日志
你有一个包含异步类的Python脚本,初始化时会启动一个无限循环的后台任务,同时故意不关闭aiohttp客户端会话,期望程序退出时将unclosed client session和unclosed connector的错误记录到test.log中,但当前保留后台任务时无法捕获到这些错误日志;移除后台任务后则能正常输出期望的错误日志。
原脚本代码:
import asyncio, aiohttp, logging logging.basicConfig(level=logging.DEBUG, filename='test.log', filemode='w') class Client: async def _async_init(self): self.session = aiohttp.ClientSession() self.bg_task = asyncio.create_task(self._background_task()) await self.make_request() logging.debug('async client initialized.') return self async def make_request(self): async with self.session.get('https://google.com') as response: self.info = await response.text() async def _background_task(self): while True: await asyncio.sleep(1000) async def main(): client = await Client()._async_init() # 故意不关闭aiohttp客户端会话... # ...以记录'unclosed client session'错误。 asyncio.run(main())
非期望输出(test.log):
DEBUG:asyncio:Using proactor: IocpProactor DEBUG:root:async client initialized.
期望输出(移除后台任务时的结果):
DEBUG:asyncio:Using proactor: IocpProactor DEBUG:root:async client initialized. ERROR:asyncio:Unclosed client session client_session: <aiohttp.client.ClientSession object at 0x000002043315D6D0> ERROR:asyncio:Unclosed connector connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x000002043312B230>, 44091.562)]', '[(<aiohttp.client_proto.ResponseHandler object at 0x000002043312B7E0>, 44092.468)]'] connector: <aiohttp.connector.TCPConnector object at 0x000002043315D710>
原因分析
后台任务是一个无限循环的asyncio.Task,asyncio.run()会在主协程结束后等待所有未完成的后台任务完成才会终止事件循环。由于后台任务永远不会结束,程序无法正常退出,也就无法触发aiohttp客户端会话的垃圾回收逻辑(__del__方法),而这些未关闭的错误日志正是在垃圾回收阶段输出的。
解决方案
在程序退出前主动取消后台任务,让事件循环可以正常终止,触发垃圾回收和错误日志输出。
修改后的完整代码:
import asyncio, aiohttp, logging logging.basicConfig(level=logging.DEBUG, filename='test.log', filemode='w') class Client: async def _async_init(self): self.session = aiohttp.ClientSession() self.bg_task = asyncio.create_task(self._background_task()) await self.make_request() logging.debug('async client initialized.') return self async def make_request(self): async with self.session.get('https://google.com') as response: self.info = await response.text() async def _background_task(self): while True: await asyncio.sleep(1000) async def main(): client = await Client()._async_init() # 取消后台任务,让程序能正常退出 client.bg_task.cancel() # 捕获任务取消异常,避免输出额外的取消日志 try: await client.bg_task except asyncio.CancelledError: pass # 故意不关闭会话,保留错误触发场景 asyncio.run(main())
效果说明
取消后台任务后,asyncio.run()可以正常终止事件循环,此时Python的垃圾回收机制会处理Client实例及其中的session对象,触发aiohttp的清理逻辑,从而将unclosed client session和unclosed connector的错误日志写入test.log中,同时保留了后台任务的逻辑。
内容的提问来源于stack exchange,提问作者UpTheIrons
相关产品推荐
相关产品推荐

