Python asyncio.gather 与 TaskGroup 取消语义怎么选
在并发请求、批量读取或并行计算里,asyncio.gather() 和 asyncio.TaskGroup 都能同时运行多个协程,但它们对“一个任务失败后怎么办”的答案完全不同。简单记忆是:只想收集一组彼此独立的结果,可以用 gather;这些任务属于同一个业务操作,任何一个失败都应该让其余任务停下并统一收尾,优先用 TaskGroup。
选择的关键不是哪个 API 更快,而是谁负责取消兄弟任务、谁等待清理完成,以及调用方最终接收到一个异常还是一组异常。
gather默认按输入顺序返回结果;首个异常会立即传播,但其他任务不会因此自动取消。TaskGroup适合有共同生命周期的任务:一个子任务失败,剩余任务会被取消,退出上下文前会等待它们结束。- 协程捕获
CancelledError后应在清理完成后继续抛出,避免破坏结构化并发的取消协议。
下面的代码只演示语义,图示也是根据代码实体绘制的结构示意,并不代表本机运行截图。
先看两种 API 管的到底是什么
gather(*aws) 接收一组可等待对象,把成功值按传入顺序聚合成列表。它关心的是“结果怎么回来”。TaskGroup 则是异步上下文管理器,任务必须在组的生命周期内创建和完成,它关心的是“这一组任务何时一起结束”。
| 场景 | 更合适的选择 | 原因 |
|---|---|---|
| 独立查询,允许部分任务继续 | gather | 一个查询失败不必打断其他查询 |
| 一次订单操作拆成多个子任务 | TaskGroup | 失败后统一取消并等待收尾 |
| 必须拿到每个任务的成功/异常值 | gather(return_exceptions=True) | 异常被放进结果列表,调用方自行分类 |

异常传播不同,兄弟任务的命运也不同
使用默认参数 return_exceptions=False 时,gather 会把第一个抛出的异常传给等待它的调用方。这个动作并不等于取消其他 awaitable:它们可能继续运行,甚至在调用方已经进入 except 后才完成。如果调用方在捕获异常后才调用已结束的 gather.cancel(),也不能补取消已经继续运行的兄弟任务。
TaskGroup 的规则更像一个失败即收口的边界。某个子任务抛出非 CancelledError 异常后,剩余任务会被取消;上下文管理器会等待这些任务完成清理,再把异常组合成 ExceptionGroup 或 BaseExceptionGroup 抛出。要分别处理异常类型,可以使用 Python 的 except*。
import asyncio
async def fetch_part(name, delay, should_fail=False):
# 每个子任务只负责自己的资源;取消时仍然执行 finally。
try:
await asyncio.sleep(delay)
if should_fail:
raise RuntimeError(f"{name} 返回错误")
return name
except asyncio.CancelledError:
# 清理完成后继续抛出,不能把取消伪装成成功。
print(f"{name} 收到取消信号")
raise
finally:
# 真实项目中可在这里关闭连接、删除临时文件或归还令牌。
pass
async def load_together():
try:
async with asyncio.TaskGroup() as group:
group.create_task(fetch_part("用户", 0.2))
group.create_task(fetch_part("库存", 0.4, should_fail=True))
group.create_task(fetch_part("优惠", 1.0))
except* RuntimeError as errors:
# TaskGroup 会把并发边界内的非取消异常组合后交给这里。
for error in errors.exceptions:
print(error)
asyncio.run(load_together())
在这个例子里,“优惠”不是因为自己的逻辑失败,而是因为“库存”失败触发了组级取消。若把三项换成 gather,默认行为是先把库存异常传出,用户和优惠任务不会自动被取消。这就是两者最容易造成生产事故的差别:调用方以为批次已经结束,实际上还有任务在后台修改状态。

把取消责任写进协程,而不是写在调用方猜
无论选择哪个 API,子协程都应把清理放进 finally。如果确实需要观察取消信号,可以捕获 CancelledError 做日志或释放动作,但动作完成后通常要重新抛出。吞掉这个异常会让上层误以为任务正常完成,也可能让 TaskGroup 或 asyncio.timeout() 的内部取消机制失去预期。
还要区分“取消 gather 本身”和“一个被 gather 收集的 Task 被取消”:前者会取消尚未完成的 awaitable;后者在 gather 中按 CancelledError 处理,并不会连带取消其他任务。需要把每个结果和异常都作为数据处理时,才使用 return_exceptions=True,并明确检查列表里的异常对象,不能直接当作业务结果。
按业务边界落地的判断清单
- 任务相互独立,某项失败后其他项仍有价值:使用
gather,并在调用方设计好异常与后台任务的归属。 - 任务共同组成一次不可拆分的操作:使用
TaskGroup,让失败、取消和清理在同一个上下文内闭合。 - 需要“成功项 + 失败项”完整报告:使用
gather(return_exceptions=True),先把异常转成明确的报告结构。 - 子任务会再创建子任务,或存在嵌套超时:优先考虑
TaskGroup,避免任务脱离父操作的生命周期。
最终可以用一句工程判断收尾:gather 更像“把几份结果装进一个列表”,TaskGroup 更像“为一组相关任务建立责任边界”。先画清楚失败后的责任,再决定 API,通常比先写代码再追取消问题省得多。
常见问题
TaskGroup 能不能像 gather 一样直接返回列表?
不能直接返回聚合列表。需要保存 create_task() 返回的 Task,在上下文退出后读取各自的 result();若组失败,先处理异常组。
gather 的 return_exceptions=True 会取消兄弟任务吗?
不会。它只是把异常作为列表元素返回;是否重试、忽略或终止其他任务仍由调用方决定。
为什么捕获 CancelledError 后还要 raise?
取消是上层生命周期控制的一部分。清理完成后继续传播,父任务才能知道该任务确实被取消,结构化并发也才能正确完成退出。
journald 如何按服务和启动批次筛选日志
- 上一篇
- journald 如何按服务和启动批次筛选日志
- 下一篇
- Go sync.WaitGroup 如何用计数快照避免 Add 竞态
-
- 文章 · python教程 | 4小时前 | 文件操作 · Python教程 · pathlib · 备份脚本 · 符号链接 目录复制 Python pathlib Path.copy Path.copy_into preserve_metadata
- Python pathlib.Path.copy 如何保留目标目录结构
- 282浏览 收藏
-
- 文章 · python教程 | 6小时前 | python · Python 3.14 · 多解释器 · InterpreterPoolExecutor ·
- Python 3.14 多解释器如何分配独立任务
- 180浏览 收藏
-
- 文章 · python教程 | 23小时前 | 字符串处理 · Python教程 · Python 3.14 · 安全渲染 · Python 模板解析 Python 3.14 t-string string.templatelib template string
- Python 3.14 template string 如何用于受控模板解析
- 418浏览 收藏
-
- 文章 · python教程 | 1天前 |
- Python sqlite3 row_factory 如何返回命名字段
- 480浏览 收藏
-
- 文章 · python教程 | 1天前 |
- Python logging Filter 修改 LogRecord 如何隔离 handler
- 391浏览 收藏
-
- 文章 · python教程 | 1天前 | python · 超时处理 · 进程管理 · 子进程 · Python subprocess.run Popen poll TimeoutExpired
- Python subprocess.run 超时后如何区分子进程已退出还是仍存活
- 213浏览 收藏
-
- 文章 · python教程 | 1天前 |
- Python typing.Protocol 运行时检查为何不等于完整实现
- 254浏览 收藏
-
- 文章 · python教程 | 1天前 | python · dataclasses · dataclass dataclasses.replace frozen
- Python dataclasses.replace 如何更新 frozen 对象
- 428浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 101次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 15次使用
-
- OpenCompass
- OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
- 29次使用
-
- AGI-Eval
- AGI-Eval是由上海交大等高校联合发布的大模型评测社区,提供公正透明的LLM能力榜单、多领域评测集及Data Studio数据服务,助力AI模型性能评估与NLP科研开发。
- 16次使用
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 255次使用
-
- go格式“占位符”输入输出 类似python的input
- 2023-01-19 346浏览
-
- Golang如何调用Python代码详解
- 2023-01-07 235浏览
-
- HTTP 的 response 中的响应体和头部是分开发送的吗?
- 2023-01-28 387浏览
-
- B站等视频网站的弹幕用的是 websocket 还是轮询?
- 2023-02-16 447浏览
-
- Linux 下有什么命令行工具以时序显示 CPU 占用率?
- 2023-01-13 360浏览

