从 loop.create_task() 引发的 python asyncio 异常
Posted
技术标签:
【中文标题】从 loop.create_task() 引发的 python asyncio 异常【英文标题】:python asyncio exceptions raised from loop.create_task() 【发布时间】:2019-09-06 13:15:03 【问题描述】:我希望我的代码使用 python logging
来记录异常。
在我使用await
的常用代码中,异常会正常引发,所以:
try:
await code_that_can_raise()
except Exception as e:
logger.exception("Exception happended")
工作正常。
但是,当使用
loop.create_task(coro())
我不确定如何在此处捕获异常。 包装 create_task() 调用显然是行不通的。 在代码中记录每个异常的最佳解决方案是什么?
【问题讨论】:
【参考方案1】:在代码中记录每个异常的最佳解决方案是什么?
如果你控制create_task
的调用,但不控制coro()
中的代码,那么你可以写一个日志包装器:
async def log_exceptions(awaitable):
try:
return await awaitable
except Exception:
logger.exception("Unhandled exception")
那么您可以拨打loop.create_task(log_exceptions(coro()))
。
如果您不能或不想包装每个create_task
,您可以调用loop.set_exception_handler
,将异常设置为您自己的函数,该函数将在您认为合适的时候记录异常。
【讨论】:
better 使用except Exception:
而不是 except:
以避免抑制 KeyboardInterrupt
和类似的非错误。
任何基于事件循环的通用解决方案?而不是包装每个 create_task 调用。因为据我了解,异常最终会引发到事件循环。
@user3599803 你可以使用set_exception_handler
来达到这个目的;见编辑回答。
如何在非异步函数中等待?
@nurettin 很好,我现在添加了缺少的async
。【参考方案2】:
刚刚提到:asyncio.Task
对象具有方法 result
和 exception
。result
:
[...] 如果协程引发异常,则该异常是 重新提出[...]
exception
:
[...] 如果被包装的协程引发了异常,该异常是 返回[...]
给定一个简单的设置(使用 Python 3.7 语法):
import asyncio
tasks =[]
async def bad_test():
raise ValueError
async def good_test():
return
async def main():
tasks.append(asyncio.create_task(bad_test()))
tasks.append(asyncio.create_task(good_test()))
asyncio.run(main())
使用result
,可以这样做:
for t in tasks:
try:
f = t.result()
except ValueError as e:
logger.exception("we're all doomed")
或者,使用exception
:
for t in tasks:
if isinstance(t.exception(), Exception):
logger.exception("apocalypse now")
但是,这两种方法都需要完成Task
,否则:
如果任务已被取消,此方法会引发 CancelledError 例外。
(result):如果任务的结果尚不可用,此方法会引发 InvalidStateError 异常。
(exception):如果 Task 还没有完成,这个方法会引发 InvalidStateError 异常。
因此,与其他答案中的建议不同,当任务中引发异常时不会发生日志记录,而是在任务完成后评估任务时发生。
【讨论】:
【参考方案3】:扩展@user4815162342 的解决方案,我在log_exceptions
周围创建了一个包装器,以避免将每个协程嵌套在两个函数中:
import asyncio
from typing import Awaitable
def create_task_log_exception(awaitable: Awaitable) -> asyncio.Task:
async def _log_exception(awaitable):
try:
return await awaitable
except Exception as e:
logger.exception(e)
return asyncio.create_task(_log_exception(awaitable))
用法:
create_task_log_exception(coroutine())
【讨论】:
以上是关于从 loop.create_task() 引发的 python asyncio 异常的主要内容,如果未能解决你的问题,请参考以下文章
python框架fastapi, AttributeError: module 'asyncio' has no attribute 'iscoroutinefunction&