如果任务对象存储在实例变量中,Starlette + asyncio.create_task()不会记录错误 [英] Starlette + asyncio.create_task() doesn't log error if task object is stored in instance variable

查看:151
本文介绍了如果任务对象存储在实例变量中,Starlette + asyncio.create_task()不会记录错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

好的,这很奇怪,但是可以了-

Okay, this is very weird, but here goes -

import asyncio

from starlette.applications import Starlette


class MyTasks:
    def __init__(self):
        self.task = None

    async def main(self):
        self.task = asyncio.create_task(self.hello())

    async def hello(self):
        raise ValueError


async def main():
    await MyTasks().main()


app = Starlette(on_startup=[main])


$ uvicorn test:app
INFO:     Started server process [26622]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

嗯,这里没有ValueError ...

Hmm, no ValueError here...

现在,在MyTasks.main()中删除对self.task的分配.

Now, remove the assignment to self.task in MyTasks.main().

    async def main(self):
        asyncio.create_task(self.hello())
    ...


$ uvicorn test:app
INFO:     Started server process [29083]
INFO:     Waiting for application startup.
ERROR:    Task exception was never retrieved
future: <Task finished name='Task-3' coro=<MyTasks.hello() done, defined at ./test.py:13> exception=ValueError()>
Traceback (most recent call last):
  File "./test.py", line 14, in hello
    raise ValueError
ValueError
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

...还有,瞧.

这是怎么回事?该分配如何构成或破坏异常记录!?

What's going on here? How does that assignment make or break the exception logging!?

推荐答案

任务是未来,这意味着它具有结果的概念.对于任务,结果是它驱动的协程返回的值.如果协程引发异常,则将该异常封装在任务对象中.正确编写的代码预期最终将等待任务或访问其结果,以免异常无声地传递.

A task is a subclass of future, which means that it has the concept of a result. In case of a task, the result is the value returned by the coroutine it drives. If the coroutine raises an exception, then the exception is encapsulated in the task object. Correctly written code is expected to eventually either await the task or access its result, in order for exceptions not to pass silently.

为帮助调试忘记访问任务的代码,如果任务引发异常但从未等待,则任务的析构函数会记录错误.无法在运行析构函数之前 记录此错误,因为只要您的代码保留在任务对象上,就可以随时等待它.析构函数运行的时间点是Python能够可靠地证明"该任务不再等待的第一个实例.

To help debug code that forgets to access the task, task's destructor logs an error if the task raised an exception but was never awaited. This error cannot be logged before running the destructor because as long as your code holds on to the task object, it might conceivable await it at any point. The point when the destructor runs is the first instance when Python can reliably "prove" that the task is unawaited.

但是您不应该依赖这种最后的日志记录,它是在尽力而为的基础上提供的.例如,由于GC,可能会推迟运行析构函数.我希望将任务分配给实例,该实例的绑定方法由该任务驱动,从而使该任务成为参考周期的一部分.这会将析构函数的运行推迟到完整的GC,直到您看不到日志为止.

But this last-ditch logging is not something you should rely on, it's provided on a best-effort basis. For example, running the destructor can be postponed due to GC. I expect that assigning the task to an instance whose bound method is being driven by the task makes the task part of a reference cycle. This postpones running the destructor until a full GC, and you don't see the log.

要解决此问题,您应该在协程中捕获异常并自己记录它们,而不是允许它们传播,或者实际上在代码中的某个时刻等待任务.

To fix the problem, you should either catch the exceptions in your coroutine and log them yourself instead of allowing them to propagate, or actually await the task at some point in your code.

这篇关于如果任务对象存储在实例变量中,Starlette + asyncio.create_task()不会记录错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆