这是一个 python 脚本,其中包含一个等待任务并在初始化时启动后台循环的异步类:
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()
# Intentionally leaving the aiohttp client session unclosed...
# ...to log the 'unclosed client session' error.
asyncio.run(main())
输出(不需要):
# test.log
DEBUG:asyncio:Using proactor: IocpProactor
DEBUG:root:async client initialized.
我想要的是将某些异步错误记录到
test.log
文件中。如果我不使用任何日志记录,这些错误通常会打印到终端。这些错误也会在程序退出时记录下来,因为它们是针对 unclosed client session
和 unclosed connector
情况记录的。
我想如果我从课堂上删除后台任务:
# These lines are removed from the class
...
# self.bg_task = asyncio.create_task(self._background_task())
...
# async def _background_loop(self):
# while True:
# await asyncio.sleep(1000)
...
输出(期望):
# test.log
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>
问题是,我怎样才能得到这个输出,同时还保留类中的后台任务?
注意我检查了 aiohttp 和 asyncio 源可能会有用。我看到 aiohttp 调用 asyncio 的
call_exception_handler
来记录这些错误,并且它们使用简单的 logger.error()
记录在 asyncio 库中的这个方法中。 asyncio 的记录器除了它的名字之外没有任何配置。
为什么
asyncio
的异常处理程序没有被调用(?),因为我的后台任务超出了我的理解,所以知道是否有人解释了那部分也很有帮助。
编辑: 目前此解决方案不起作用。我使用的是 Python 3.11,但不确定我在发布此内容时是否使用的是旧版本。此外,在进一步阅读之后,我开始承认像这样抑制
asyncio.CancelledError
不是好的做法,并且在 Python 文档中是不鼓励的。我从我的后台任务中删除了 try/except 块,只有当我确实有充分的理由来捕获错误时才在我的协程中使用它们。 (例如,日志记录或其他异常处理)这是另一个对我有帮助的问题.
编辑前回答:
我意识到我必须在事件循环关闭之前终止或等待所有异步任务才能完成这项工作。我不确定为什么会这样,但这个想法奏效了。
由于我的后台任务永远持续下去,我需要杀死(取消)它而不是等待它。调用
task.cancel()
不足以终止任务,它只是通过将 asyncio.CancelledError
异常抛入任务协程来发出取消请求。 这个答案 有助于了解更多关于任务创建和终止的信息。
当事件循环关闭时,任务会自动取消(将
asyncio.CancelledError
抛入任务中)因此我们需要完成事件循环开始时的取消。我们也可以手动调用task.cancel()
,但在这里我记录了用户错误,因此任何类型的手动取消/终止都不适合这种情况。
也许不是最好的,但我的解决方案是通过更改
asyncio.CancelledError
功能来捕获 _background_task
异常以确保完成取消,例如:
...
async def _background_task(self):
try:
while True:
await asyncio.sleep(1000)
except asyncio.CancelledError:
pass # catch the exception that the event loop throws
...
最终运行脚本后,成功记录了所需的错误:
# test.log
DEBUG:asyncio:Using proactor: IocpProactor
DEBUG:root:async client initialized.
ERROR:asyncio:Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x000001187C9F9C90>
ERROR:asyncio:Unclosed connector
connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x000001187C9C7690>, 24429.703)]', '[(<aiohttp.client_proto.ResponseHandler object at 0x000001187C9C7C40>, 24430.015)]']
connector: <aiohttp.connector.TCPConnector object at 0x000001187C9F9CD0>