如何正确等待asyncio任务完成取消操作?
Asyncio任务取消的两难困境
取消操作的文档表明,通常应传播CancelledError异常:
因此,与Future.cancel()不同,Task.cancel()无法保证任务会被取消,不过完全抑制取消操作并不常见,且不被推荐。如果协程仍决定抑制取消,除捕获异常外,还需调用Task.uncancel()。
但检测取消的两个方法都不可等待:cancelling用于判断取消是否正在进行,cancelled用于判断取消是否完成。因此等待取消的直观写法如下:
foo_task.cancel() try: await foo_task except asyncio.CancelledError: pass
网上甚至Stack Overflow上都有很多这样的示例。但文档警告称,这样做会导致asyncio机制“异常运行”:
实现结构化并发的asyncio组件(如asyncio.TaskGroup和asyncio.timeout())内部使用取消操作,如果协程吞噬asyncio.CancelledError,这些组件可能会异常运行。
之所以要阻塞等待任务完全取消,是因为asyncio事件循环仅对任务创建弱引用。当类关闭时(例如通过cleanup方法或__aexit__),如果不等待所有生成的任务完成,可能会在任务仍运行时销毁唯一的强引用,此时Python会抛出错误:
ERROR base_events.py:1771: Task was destroyed but it is pending!
如此一来,为避免该错误,似乎不得不做不被推荐的操作。唯一的替代方案似乎是将所有创建的任务存入全局集合,并在运行结束时等待全部完成,但这是种不符合Python风格的怪异技巧。
内容的提问来源于stack exchange,提问作者Joseph Garvin
相关产品推荐
相关产品推荐

