从 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 对象具有方法 resultexceptionresult:

[...] 如果协程引发异常,则该异常是 重新提出[...]

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 异常的主要内容,如果未能解决你的问题,请参考以下文章

python3 - 从异步方法获取结果

python框架fastapi, AttributeError: module 'asyncio' has no attribute 'iscoroutinefunction&

从可变参数函数的调用引发异常崩溃一例引发的一些思考

从 django 模型的保存方法中引发 ValidationError?

如何从用户控件中引发自定义路由事件?

如果从 Fragment 调用 DialogFragment 将引发 ClassCastException