YNX*_*YNX 1 python python-asyncio
这是代码,我认为程序会因为未捕获的异常而立即崩溃。然而,当主要任务coro2
完成时,它等待了 10 秒。
import asyncio
@asyncio.coroutine
def coro1():
print("coro1 primed")
yield
raise Exception("abc")
@asyncio.coroutine
def coro2(loop):
try:
print("coro2 primed")
ts = [asyncio.Task(coro1(),loop=loop) for _ in range(2)]
res = yield from asyncio.sleep(10)
print(res)
except Exception as e:
print(e)
raise
loop= asyncio.get_event_loop()
loop.run_until_complete(coro2(loop))
Run Code Online (Sandbox Code Playgroud)
我认为这是一个严重的问题,因为在更复杂的程序中,这会使进程永远卡住,而不是因异常信息而崩溃。
except
此外,我在源代码中的块中设置了断点run_until_complete
,但它没有被触发。我感兴趣的是哪一段代码在 python asyncio 中处理了该异常。
首先,没有理由在 Python 中使用基于生成器的协程以及多年来可用的 async/await 语法,并且装饰coroutine
器现已弃用并计划删除。此外,您不需要将事件循环传递给每个协程,您可以随时asyncio.get_event_loop()
在需要时获取它。但这些与你的问题无关。
except
中的块没有coro2
触发,因为中引发的异常coro1
没有传播到coro2
。这是因为您显式地coro1
作为任务运行,该任务在后台执行,并且不等待它。您应该始终确保您的任务得到等待,这样异常就不会被忽视;系统地执行此操作有时称为结构化并发。
正确的写法应该是这样的:
async def coro1():
print("coro1 primed")
await asyncio.sleep(0) # yield to the event loop
raise Exception("abc")
async def coro2():
try:
print("coro2 primed")
ts = [asyncio.create_task(coro1()) for _ in range(2)]
await asyncio.sleep(10)
# ensure we pick up results of the tasks that we've started
for t in ts:
await t
except Exception as e:
print(e)
raise
asyncio.run(coro2())
Run Code Online (Sandbox Code Playgroud)
请注意,这将运行sleep()
到完成,然后才会传播后台任务引发的异常。如果您想立即传播,可以使用asyncio.gather()
,在这种情况下,您不必首先显式创建任务:
async def coro2():
try:
print("coro2 primed")
res, *ignored = await asyncio.gather(
asyncio.sleep(10),
*[(coro1()) for _ in range(2)]
)
print(res)
except Exception as e:
print(e)
raise
Run Code Online (Sandbox Code Playgroud)
我感兴趣的是哪一段代码在 python asyncio 中处理了该异常。
协程引发的未处理的异常将被 asyncio 捕获并存储在任务对象中。这允许您等待任务或(如果您知道任务已完成)使用该result()
方法获取其结果,这两种方法都会传播(重新引发)异常。由于您的代码从未访问过任务的结果,因此异常实例仍然被遗忘在任务对象内。Python 到目前为止注意到了这一点,并在任务对象被销毁时打印“任务异常从未检索到”警告以及回溯,但此警告是在尽力而为的基础上提供的,通常来得太晚了,不应该所依靠。
归档时间: |
|
查看次数: |
5475 次 |
最近记录: |