cancel的文档听起来你通常应该传播 CancelledError 异常:
因此,与 Future.cancel() 不同,Task.cancel() 并不能保证 Task 将被取消,尽管完全抑制取消并不常见并且被积极劝阻。如果协程仍然决定抑制取消,除了捕获异常之外,它还需要调用 Task.uncancel() 。
但是,这两种检测取消的方法都是不可等待的:
cancelling
告诉您取消是否正在进行,cancelled
告诉您取消是否已完成。因此,等待取消的明显方法是:
foo_task.cancel()
try:
await foo_task
except asyncio.CancelledError:
pass
即使在 SO 上,网上也有很多这样的例子。 但是文档警告您,如果您这样做,异步机器将“行为不当”:
支持结构化并发的 asyncio 组件,例如 asyncio.TaskGroup 和 asyncio.timeout(),是在内部使用取消实现的,如果协程吞下 asyncio.CancelledError,则可能会出现错误行为
现在您可能想知道为什么要等到任务完全取消才阻塞。问题是 asyncio 事件循环仅创建对任务的弱引用,因此,如果您的类正在关闭(例如由于
cleanup
方法或 __aexit__
)并且您不等待您生成的每个任务,那么您可能会在任务仍在运行时拆掉唯一的强引用,然后 python 就会对你大喊大叫:
ERROR base_events.py:1771: Task was destroyed but it is pending!
因此,这似乎避免了我专门被迫做我不应该做的事情的错误:P 唯一的选择似乎是奇怪的非Pythonic hackery,比如将我所做的每个任务填充到全局集中并等待它们全部跑步结束。
您遇到清理问题。
with
语句一般用来解决清理问题。
使用
asyncio.TaskGroup
:
async with asyncio.TaskGroup() as tg:
tg.create_task(some_coro(...)).cancel()