asyncio.wait_for 不会传播 CancelledError,如果等待未来是 "done" 取消之前

asyncio.wait_for does not propagate CancelledError, if waited on future is "done" before cancellation

我正在开发一个具有可由用户启动的任务的应用程序。这些任务也可以由用户中止。值得注意的是,取消可以在任何给定时间发生。我通过使用 asyncio.tasks.Task 实现了这一点,如果用户中止,我会取消它。我最近从 Python 3.8.5 更新到 3.10(在 Windows 10),之后我的 Tasks 的取消按预期停止工作。

经过一些挖掘,我想我发现了哪里出了问题:这些任务自然涉及到很多 I/O 也是不可靠的,所以我正在使用 [=17= 形式的超时].如果等待的未来在取消时已经完成,那么 CancelledError 似乎会在 wait_for() 内被吞没。考虑来自 Python 3.8.5:

的代码片段
# asyncio.tasks.wait_for() / Python 3.8.5
# wait until the future completes or the timeout
        try:
            await waiter
        except futures.CancelledError:
            fut.remove_done_callback(cb)
            fut.cancel()
            raise # reraise CancelledError -> all is fine

        if fut.done():
            return fut.result()
        else:
            fut.remove_done_callback(cb)
            # We must ensure that the task is not running
            # after wait_for() returns.
            # See https://bugs.python.org/issue32751
            await _cancel_and_wait(fut, loop=loop)
            raise futures.TimeoutError()

对比 Python 3.10:

# asyncio.tasks.wait_for() / Python 3.10
# wait until the future completes or the timeout
        try:
            await waiter
        except exceptions.CancelledError:
            if fut.done(): # future already done
                return fut.result() # return result without raising -> task is not aborted
            else:
                fut.remove_done_callback(cb)
                # We must ensure that the task is not running
                # after wait_for() returns.
                # See https://bugs.python.org/issue32751
                await _cancel_and_wait(fut, loop=loop)
                raise

这段代码重现错误:

import asyncio

async def im_done():
    print("done")

async def nested_wait(timeout):
    await asyncio.wait_for(im_done(), timeout=timeout)

async def test_cancel():
    task = asyncio.create_task(nested_wait(30))
    await asyncio.sleep(0)
    task.cancel()
    await asyncio.sleep(0)
    
    await task # raises on 3.8.5 / doesn't raise on 3.10

asyncio.run(test_cancel())

这是预期的行为还是错误?请注意,我示例中的 im_done() 实际上是一个执行实际 I/O 的协程。我的中止测试总是失败,但我不完全理解未来的“完成”状态是如何发生的,以及如果等待的未来已经完成,为什么 CancelledErrorwait_for() 内引发.如果纯粹是偶然和时机,或者如果我实际上使用了 API 错误。但是由于在 asyncio.tasks.wait_for 中的代码更改之前一切都运行良好,我猜这可能是一个错误。

没关系,这似乎是一个较旧的错误,只是尚未解决。它在 Pyton 的问题跟踪器中有一个 open issue。我第一次使用问题跟踪器时没有看到它。当然我在post这里之后就找到了。无论如何,我都会将问题留给遇到相同问题但在问题跟踪器中遗漏的任何人。