python asyncio.Task 中的异常在主任务完成之前不会引发

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 中处理了该异常。

use*_*342 8

首先,没有理由在 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 到目前为止注意到了这一点,并在任务对象被销毁时打印“任务异常从未检索到”警告以及回溯,但此警告是在尽力而为的基础上提供的,通常来得太晚了,不应该所依靠。