如果任务对象存储在实例变量中,Starlette + asyncio.create_task() 不会记录错误
Starlette + asyncio.create_task() doesn't log error if task object is stored in instance variable
好吧,这很奇怪,但是-
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
...
现在,删除 MyTasks.main()
中对 self.task
的分配。
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)
... 瞧。
这是怎么回事?该分配如何建立或破坏异常记录!?
任务是future的子类,也就是说它有结果的概念。在任务的情况下,结果是它驱动的协程返回的值。如果协程引发异常,则异常会封装在任务对象中。正确编写的代码应该最终等待任务或访问其结果,以便异常不会静默传递。
为了帮助调试忘记访问任务的代码,如果任务引发异常但从未等待,则任务的析构函数会记录错误。此错误无法在 在 运行 析构函数之前记录,因为只要您的代码持有任务对象,就可以想象它随时等待它。析构函数运行的时间点是 Python 可以可靠地 "prove" 未等待任务的第一个实例。
但是这种最后的日志记录不是您应该依赖的东西,它是在尽力而为的基础上提供的。例如,运行 析构函数可以因 GC 而推迟。我希望将任务分配给一个实例,该实例的绑定方法由该任务驱动,从而使该任务成为引用循环的一部分。这会将 运行 析构函数推迟到完全 GC 之前,并且您看不到日志。
要解决此问题,您应该在协程中捕获异常并自己记录它们而不是让它们传播,或者在代码中的某个时刻实际等待任务。
抱歉,这是 this other question 的骗局。
asyncio.create_task()
has been posted there.
的直接替代品
这是使用替换 create_task()
后的输出
案例 1:使用 await self.task
$ uvicorn test:app
INFO: Started server process [33213]
INFO: Waiting for application startup.
ERROR: Traceback (most recent call last):
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/starlette/routing.py", line 517, in lifespan
await self.startup()
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/starlette/routing.py", line 494, in startup
await handler()
File "./test.py", line 35, in main
await MyTasks().main()
File "./test.py", line 27, in main
print(await self.task)
File "./test.py", line 13, in wrapper
return await task
File "./test.py", line 30, in hello
raise ValueError
ValueError
ERROR: Application startup failed. Exiting.
情况 2:没有 await self.task
$ uvicorn test:app
INFO: Started server process [32627]
INFO: Waiting for application startup.
INFO: Application startup complete.
ERROR: Exception in callback <function create_task.<locals>.on_done at 0x10c519550>
handle: <Handle create_task.<locals>.on_done created at /Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/uvicorn/main.py:382>
source_traceback: Object created at (most recent call last):
File "/Users/dev/.virtualenvs/server-99338def/bin/uvicorn", line 8, in <module>
sys.exit(main())
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/click/core.py", line 764, in __call__
return self.main(*args, **kwargs)
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/click/core.py", line 717, in main
rv = self.invoke(ctx)
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/click/core.py", line 956, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/click/core.py", line 555, in invoke
return callback(*args, **kwargs)
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/uvicorn/main.py", line 331, in main
run(**kwargs)
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/uvicorn/main.py", line 354, in run
server.run()
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/uvicorn/main.py", line 382, in run
loop.run_until_complete(self.serve(sockets=sockets))
Traceback (most recent call last):
File "uvloop/cbhandles.pyx", line 70, in uvloop.loop.Handle._run
File "./test.py", line 9, in on_done
fut.result()
File "./test.py", line 30, in hello
raise ValueError
ValueError
好吧,这很奇怪,但是-
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
...
现在,删除 MyTasks.main()
中对 self.task
的分配。
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)
... 瞧。
这是怎么回事?该分配如何建立或破坏异常记录!?
任务是future的子类,也就是说它有结果的概念。在任务的情况下,结果是它驱动的协程返回的值。如果协程引发异常,则异常会封装在任务对象中。正确编写的代码应该最终等待任务或访问其结果,以便异常不会静默传递。
为了帮助调试忘记访问任务的代码,如果任务引发异常但从未等待,则任务的析构函数会记录错误。此错误无法在 在 运行 析构函数之前记录,因为只要您的代码持有任务对象,就可以想象它随时等待它。析构函数运行的时间点是 Python 可以可靠地 "prove" 未等待任务的第一个实例。
但是这种最后的日志记录不是您应该依赖的东西,它是在尽力而为的基础上提供的。例如,运行 析构函数可以因 GC 而推迟。我希望将任务分配给一个实例,该实例的绑定方法由该任务驱动,从而使该任务成为引用循环的一部分。这会将 运行 析构函数推迟到完全 GC 之前,并且您看不到日志。
要解决此问题,您应该在协程中捕获异常并自己记录它们而不是让它们传播,或者在代码中的某个时刻实际等待任务。
抱歉,这是 this other question 的骗局。
asyncio.create_task()
has been posted there.
这是使用替换 create_task()
案例 1:使用 await self.task
$ uvicorn test:app
INFO: Started server process [33213]
INFO: Waiting for application startup.
ERROR: Traceback (most recent call last):
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/starlette/routing.py", line 517, in lifespan
await self.startup()
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/starlette/routing.py", line 494, in startup
await handler()
File "./test.py", line 35, in main
await MyTasks().main()
File "./test.py", line 27, in main
print(await self.task)
File "./test.py", line 13, in wrapper
return await task
File "./test.py", line 30, in hello
raise ValueError
ValueError
ERROR: Application startup failed. Exiting.
情况 2:没有 await self.task
$ uvicorn test:app
INFO: Started server process [32627]
INFO: Waiting for application startup.
INFO: Application startup complete.
ERROR: Exception in callback <function create_task.<locals>.on_done at 0x10c519550>
handle: <Handle create_task.<locals>.on_done created at /Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/uvicorn/main.py:382>
source_traceback: Object created at (most recent call last):
File "/Users/dev/.virtualenvs/server-99338def/bin/uvicorn", line 8, in <module>
sys.exit(main())
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/click/core.py", line 764, in __call__
return self.main(*args, **kwargs)
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/click/core.py", line 717, in main
rv = self.invoke(ctx)
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/click/core.py", line 956, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/click/core.py", line 555, in invoke
return callback(*args, **kwargs)
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/uvicorn/main.py", line 331, in main
run(**kwargs)
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/uvicorn/main.py", line 354, in run
server.run()
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/uvicorn/main.py", line 382, in run
loop.run_until_complete(self.serve(sockets=sockets))
Traceback (most recent call last):
File "uvloop/cbhandles.pyx", line 70, in uvloop.loop.Handle._run
File "./test.py", line 9, in on_done
fut.result()
File "./test.py", line 30, in hello
raise ValueError
ValueError