默认情况下asyncio.gather()遇子任务取消会直接抛出CancelledError并中断执行;启用return_exceptions=True后,CancelledError作为结果项返回,需用isinstance(res, asyncio.CancelledError)显式判断。

gather 中的 CancelledError 默认不传播
默认情况下,asyncio.gather() 遇到某个子任务被取消时,会把 CancelledError 包装进返回的异常列表(如果用了 return_exceptions=True),否则直接抛出——但这个抛出行为是“中断整个 gather”,其余未完成任务会被一并取消,且你拿不到它们的原始状态或错误。这不是“统一处理”,而是被动崩溃。
用 return_exceptions=True + 显式检查每个结果
真正可控的方式是启用 return_exceptions=True,让所有异常(包括 CancelledError)都作为结果项返回,再遍历判断:
import asyncioasync def task_a(): await asyncio.sleep(1) return "a"
async def task_b(): raise asyncio.CancelledError() # 模拟被 cancel
async def task_c(): await asyncio.sleep(0.5) return "c"
async def main(): tasks = [task_a(), task_b(), task_c()] results = await asyncio.gather(*tasks, return_exceptions=True)
for i, res in enumerate(results): if isinstance(res, asyncio.CancelledError): print(f"task {i} was cancelled") elif isinstance(res, Exception): print(f"task {i} raised {type(res).__name__}: {res}") else: print(f"task {i} succeeded: {res}")asyncio.run(main())
-
return_exceptions=True是前提,否则CancelledError会立刻中断执行 - 必须用
isinstance(res, asyncio.CancelledError)判断,不能只靠except CancelledError—— 因为它已不是被抛出,而是被“返回” - 注意:即使某 task 已被 cancel,它仍可能在取消前完成了部分逻辑(比如刚写完日志),所以
CancelledError出现在结果里,不代表它完全没执行
想彻底屏蔽 CancelledError?别直接吞掉
有时你想忽略取消、只关心成功值。但直接在每个协程里 try/except CancelledError: pass 是危险的——这会让协程无法响应上层取消信号,破坏 asyncio 的协作取消机制。
- 正确做法是:让子协程保持可取消性,只在
gather结果层过滤 - 例如提取所有非异常结果:
[r for r in results if not isinstance(r, BaseException)] - 若需区分“主动取消”和“其他异常”,保留
CancelledError单独处理,不要和ValueError等混在一起except Exception
超时场景下 CancelledError 最容易被误判
用 asyncio.wait_for() 包裹单个 task 后传给 gather,超时触发的取消会产生 TimeoutError,但被 wait_for 内部转成了 CancelledError 抛给子协程。此时你在 gather 结果里看到的 CancelledError,其实源头是超时,不是手动调用 task.cancel()。
- 这种间接取消无法从异常类型本身分辨,需要结合上下文(比如是否用了
wait_for) - 若需统一标记“超时导致的取消”,建议在外层封装一层自定义异常,而不是依赖
CancelledError的原始形态 -
asyncio.shield()不能防止wait_for的取消,它只防外部直接 cancel;这点常被忽略
实际中,最易被跳过的点是:以为 return_exceptions=True 后就能用 except CancelledError 捕获——其实不行,它已经变成普通返回值了。










