当前位置:首页 > 文章列表 > 文章 > python教程 > Python asyncio.gather 与 TaskGroup 取消语义怎么选

Python asyncio.gather 与 TaskGroup 取消语义怎么选

来源:17golang原创 2026-09-12 15:29:06 0浏览 收藏

在并发请求、批量读取或并行计算里,asyncio.gather()asyncio.TaskGroup 都能同时运行多个协程,但它们对“一个任务失败后怎么办”的答案完全不同。简单记忆是:只想收集一组彼此独立的结果,可以用 gather;这些任务属于同一个业务操作,任何一个失败都应该让其余任务停下并统一收尾,优先用 TaskGroup

选择的关键不是哪个 API 更快,而是谁负责取消兄弟任务、谁等待清理完成,以及调用方最终接收到一个异常还是一组异常。
要点速览
  • gather 默认按输入顺序返回结果;首个异常会立即传播,但其他任务不会因此自动取消。
  • TaskGroup 适合有共同生命周期的任务:一个子任务失败,剩余任务会被取消,退出上下文前会等待它们结束。
  • 协程捕获 CancelledError 后应在清理完成后继续抛出,避免破坏结构化并发的取消协议。

下面的代码只演示语义,图示也是根据代码实体绘制的结构示意,并不代表本机运行截图。

先看两种 API 管的到底是什么

gather(*aws) 接收一组可等待对象,把成功值按传入顺序聚合成列表。它关心的是“结果怎么回来”。TaskGroup 则是异步上下文管理器,任务必须在组的生命周期内创建和完成,它关心的是“这一组任务何时一起结束”。

场景更合适的选择原因
独立查询,允许部分任务继续gather一个查询失败不必打断其他查询
一次订单操作拆成多个子任务TaskGroup失败后统一取消并等待收尾
必须拿到每个任务的成功/异常值gather(return_exceptions=True)异常被放进结果列表,调用方自行分类
Python asyncio.gather 与 TaskGroup 的结果聚合边界和任务组生命周期关系示意图
图1:结构示意图对比 gather 的结果聚合边界与 TaskGroup 的任务组生命周期,不是运行截图。

异常传播不同,兄弟任务的命运也不同

使用默认参数 return_exceptions=False 时,gather 会把第一个抛出的异常传给等待它的调用方。这个动作并不等于取消其他 awaitable:它们可能继续运行,甚至在调用方已经进入 except 后才完成。如果调用方在捕获异常后才调用已结束的 gather.cancel(),也不能补取消已经继续运行的兄弟任务。

TaskGroup 的规则更像一个失败即收口的边界。某个子任务抛出非 CancelledError 异常后,剩余任务会被取消;上下文管理器会等待这些任务完成清理,再把异常组合成 ExceptionGroupBaseExceptionGroup 抛出。要分别处理异常类型,可以使用 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,默认行为是先把库存异常传出,用户和优惠任务不会自动被取消。这就是两者最容易造成生产事故的差别:调用方以为批次已经结束,实际上还有任务在后台修改状态。

Python TaskGroup 失败后取消兄弟任务并等待 CancelledError 清理的静态关系示意图
图2:静态关系示意图展示失败任务、组级取消、兄弟任务和 finally 清理之间的约束,不是实际运行结果。

把取消责任写进协程,而不是写在调用方猜

无论选择哪个 API,子协程都应把清理放进 finally。如果确实需要观察取消信号,可以捕获 CancelledError 做日志或释放动作,但动作完成后通常要重新抛出。吞掉这个异常会让上层误以为任务正常完成,也可能让 TaskGroupasyncio.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?

取消是上层生命周期控制的一部分。清理完成后继续传播,父任务才能知道该任务确实被取消,结构化并发也才能正确完成退出。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
journald 如何按服务和启动批次筛选日志journald 如何按服务和启动批次筛选日志
上一篇
journald 如何按服务和启动批次筛选日志
Go sync.WaitGroup 如何用计数快照避免 Add 竞态
下一篇
Go sync.WaitGroup 如何用计数快照避免 Add 竞态
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    101次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    15次使用
  • OpenCompass大模型评测体系详解:功能、使用指南与应用场景
    OpenCompass
    OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
    29次使用
  • AGI-Eval大模型评测平台:权威榜单、数据集与人机协同评测方案
    AGI-Eval
    AGI-Eval是由上海交大等高校联合发布的大模型评测社区,提供公正透明的LLM能力榜单、多领域评测集及Data Studio数据服务,助力AI模型性能评估与NLP科研开发。
    16次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    255次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码