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),之后我的 Task
s 的取消按预期停止工作。
经过一些挖掘,我想我发现了哪里出了问题:这些任务自然涉及到很多 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 的协程。我的中止测试总是失败,但我不完全理解未来的“完成”状态是如何发生的,以及如果等待的未来已经完成,为什么 CancelledError
在 wait_for()
内引发.如果纯粹是偶然和时机,或者如果我实际上使用了 API 错误。但是由于在 asyncio.tasks.wait_for
中的代码更改之前一切都运行良好,我猜这可能是一个错误。
没关系,这似乎是一个较旧的错误,只是尚未解决。它在 Pyton 的问题跟踪器中有一个 open issue。我第一次使用问题跟踪器时没有看到它。当然我在post这里之后就找到了。无论如何,我都会将问题留给遇到相同问题但在问题跟踪器中遗漏的任何人。
我正在开发一个具有可由用户启动的任务的应用程序。这些任务也可以由用户中止。值得注意的是,取消可以在任何给定时间发生。我通过使用 asyncio.tasks.Task
实现了这一点,如果用户中止,我会取消它。我最近从 Python 3.8.5 更新到 3.10(在 Windows 10),之后我的 Task
s 的取消按预期停止工作。
经过一些挖掘,我想我发现了哪里出了问题:这些任务自然涉及到很多 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 的协程。我的中止测试总是失败,但我不完全理解未来的“完成”状态是如何发生的,以及如果等待的未来已经完成,为什么 CancelledError
在 wait_for()
内引发.如果纯粹是偶然和时机,或者如果我实际上使用了 API 错误。但是由于在 asyncio.tasks.wait_for
中的代码更改之前一切都运行良好,我猜这可能是一个错误。
没关系,这似乎是一个较旧的错误,只是尚未解决。它在 Pyton 的问题跟踪器中有一个 open issue。我第一次使用问题跟踪器时没有看到它。当然我在post这里之后就找到了。无论如何,我都会将问题留给遇到相同问题但在问题跟踪器中遗漏的任何人。