科技 · 实用指南
等待已取消任务时收到 CancelledError?不要吞掉控制器自身的关闭信号
子任务结束取消的同时,控制器也可能被外部取消。先明确任务所有权,让子任务在 finally 清理,再用不会笼统吞异常的方式收集结果并验证外部取消仍会传播。

子任务结束取消的同时,控制器也可能被外部取消。先明确任务所有权,让子任务在 finally 清理,再用不会笼统吞异常的方式收集结果并验证外部取消仍会传播。
CancelledError 继承 BaseException,清理后通常应重新抛出。 Task.cancel 只请求取消,不保证协程接受。
1. 列出任务所有者与关闭触发源
列出控制器、它拥有的每个子任务、谁能取消它们,以及一个子任务失败是否应取消其余任务。不要从 except CancelledError: pass 开始。
等待子任务时看到的 CancelledError 可能来自子任务,也可能是控制器收到独立取消。仅凭异常类型不能证明来源。
2. 把子任务清理放进 finally
在子任务的 try/finally 中释放其资源,清理后通常继续传播 CancelledError。取消会在 await 注入,清理中的另一个 await 也可能再次被取消。
同步清理应短小;必须完成异步收尾时要设置明确且很小的边界并记录是否允许 shield,不能让 shield 造成无限关机。
3. 选择 TaskGroup 或 gather 语义
当作用域拥有全部子任务且同伴失败应联动取消时用 TaskGroup;退出上下文会等待它们。子任务不能吞取消,否则结构化并发会失效。
独立收集已取消子任务结果时,可用 gather(return_exceptions=True)。官方文档同时保证:gather 自身被取消仍会传播,与该参数无关。
4. 保留控制器的取消信号
不要在 await child 外笼统捕获。若必须手工判断,记录 child.done、child.cancelled 与 current_task().cancelling;控制器有待处理取消时必须重新抛出。
不要只为清零状态调用 uncancel。Python 把它描述为内部结构化并发工具;抑制取消必须是刻意且完整的特殊决定。
5. 测试同时取消与最终退出
测试三种情况:只取消子任务、控制器等待时只取消控制器、同一轮同时取消。断言资源关闭、任务状态和取消是否到达控制器调用者。
退出时只检查自己拥有的任务,不要取消无关循环任务。确认无 owned task pending、无异常未取回,timeout 路径也没有把外部关闭变成成功。
