【问题标题】:python asyncio exceptions raised from loop.create_task()从 loop.create_task() 引发的 python asyncio 异常
【发布时间】: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() 调用显然是行不通的。 在代码中记录每个异常的最佳解决方案是什么?

【问题讨论】:

    标签: python exception logging python-asyncio python-logging


    【解决方案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 对象具有方法 resultexception
    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())
      

      【讨论】:

        猜你喜欢
        • 2018-06-21
        • 1970-01-01
        • 1970-01-01
        • 2016-03-30
        • 1970-01-01
        • 2019-03-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多