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教程 | 19小时前 |
- Python aifc 替代方案 迁移旧 AIFF 读取代码前要确认哪些格式条件
- 144浏览 收藏
-
- 文章 · python教程 | 19小时前 | python ·
- Python wave.Wave_read 怎样读取 PCM 声道数和采样宽度
- 243浏览 收藏
-
- 文章 · python教程 | 19小时前 | xml · Python教程 · Python SAX xml.sax.handler.ContentHandler characters XML文本解析
- Python xml.sax.handler.ContentHandler 怎样收集跨回调的文本内容
- 382浏览 收藏
-
- 文章 · python教程 | 19小时前 |
- Python email.policy.clone 怎样设置邮件序列化换行规则
- 483浏览 收藏
-
- 文章 · python教程 | 19小时前 | Python教程 · Python 邮件解析 email.headerregistry.Address 邮箱显示名 addr_spec
- Python email.headerregistry.Address 怎样区分邮箱显示名与地址
- 308浏览 收藏
-
- 文章 · python教程 | 19小时前 |
- Python decimal.Context.flags 怎样记录计算中的舍入信号
- 276浏览 收藏
-
- 文章 · python教程 | 19小时前 |
- Python statistics.kde_random 怎样按核密度估计生成样本
- 208浏览 收藏
-
- 文章 · python教程 | 19小时前 | 标准库 · python · Python overlap statistics NormalDist
- Python statistics.NormalDist.overlap 怎样比较两个正态分布重叠程度
- 182浏览 收藏
-
- 文章 · python教程 | 19小时前 |
- Python fractions.Fraction.limit_denominator 怎样用小分母近似测量值
- 401浏览 收藏
-
- 文章 · python教程 | 19小时前 |
- Python graphlib.CycleError 怎样读取依赖环中的节点
- 324浏览 收藏
-
- 文章 · python教程 | 19小时前 |
- Python string.templatelib 怎样读取模板中的插值对象
- 221浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 749次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 745次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 831次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 796次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 630次使用
-
- 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浏览

