当前位置:首页 > 文章列表 > 文章 > python教程 > Python TaskGroup 怎样让子任务失败统一收敛

Python TaskGroup 怎样让子任务失败统一收敛

来源:17golang原创 2026-11-12 01:52:24 0浏览 收藏

如果一组异步任务属于同一个业务操作,通常不应该让调用方逐个等待、逐个猜测失败原因。asyncio.TaskGroup 的核心做法是:一个子任务先抛出非 CancelledError 异常时,取消同组其他任务,等待它们完成清理,然后在 async with 退出处把仍然需要报告的异常集中成 ExceptionGroup。

官方地址:https://docs.python.org/3/library/asyncio-task.html#task-groups

这意味着调用方只需要在任务组边界处理一次失败;子任务则要遵守取消协议,在 finally 中释放连接、文件或临时状态,并在清理完成后继续传播取消。

TaskGroup 的失败会怎样传播

TaskGroup 适合表达“这些任务要一起成功,任意一个关键任务失败就结束这一组”的关系。它不是把所有任务都跑完再收集结果:首个非取消异常会成为收敛信号,剩余任务会收到取消请求。

Python TaskGroup 中成功任务、失败任务、取消收尾和 ExceptionGroup 的关系说明图
图1:TaskGroup 失败传播与取消收尾结构图;用于理解关系,不代表运行截图。

例如“读取配置、库存和价格”属于同一个页面响应,库存请求失败后,继续等待价格请求就没有意义。TaskGroup 会先让价格任务收尾,再把库存任务的异常带到上下文外。这样可以避免后台任务在主流程已经失败后继续占用连接。

先写出正确的取消收尾

取消不是普通业务异常。子任务在下一个可中断的 await 处收到 CancelledError,应使用 finally 做清理;如果显式捕获它,清理结束后通常还要重新抛出。吞掉取消会让 TaskGroup 的退出过程失去可靠边界。

import asyncio


async def load_part(name: str, delay: float, should_fail: bool = False) -> str:
    resource = f"连接:{name}"
    try:
        # 模拟一次可被取消的异步 I/O。
        await asyncio.sleep(delay)
        if should_fail:
            raise ValueError(f"{name} 返回业务错误")
        return f"{name} 已完成"
    except asyncio.CancelledError:
        # 记录取消或撤销临时状态,清理后继续传播取消。
        raise
    finally:
        # 真实代码在这里关闭连接、释放锁或删除临时文件。
        resource = None


async def main() -> None:
    try:
        async with asyncio.TaskGroup() as group:
            group.create_task(load_part("配置", 0.2))
            group.create_task(load_part("库存", 0.1, should_fail=True))
            group.create_task(load_part("价格", 2.0))
    except* ValueError as errors:
        # 只处理业务失败;同组被取消的任务不会作为普通错误上报。
        for error in errors.exceptions:
            print(f"需要重试或降级: {error}")


asyncio.run(main())

这段代码的关键不在于打印错误,而在于 load_part 没有把取消当成成功返回。finally 无论任务成功、失败还是被取消都会执行;except asyncio.CancelledError 只负责完成取消前的必要动作,然后用 raise 保留取消状态。

用 except* 拆分统一收敛的异常

TaskGroup 退出时,多个非取消异常会被组合进 ExceptionGroup。Python 的 except* 可以按异常类型匹配其中一部分,让业务异常、超时异常和未知异常分别进入不同处理分支。它和普通 except 的区别是:处理的是异常组中的匹配成员。

import asyncio


async def call_service(name: str) -> None:
    # 每个子任务代表一个可以独立失败的外部调用。
    await asyncio.sleep(0)
    if name == "库存":
        raise ValueError("库存数据格式不正确")
    if name == "价格":
        raise TimeoutError("价格服务响应超时")


async def run_batch() -> None:
    try:
        async with asyncio.TaskGroup() as group:
            group.create_task(call_service("配置"))
            group.create_task(call_service("库存"))
            group.create_task(call_service("价格"))
    except* ValueError as errors:
        # 业务数据错误可以转为降级结果或告警。
        for error in errors.exceptions:
            print(f"业务失败: {error}")
    except* TimeoutError as errors:
        # 超时通常进入重试、兜底缓存或快速失败策略。
        for error in errors.exceptions:
            print(f"超时失败: {error}")


asyncio.run(run_batch())

实际运行时,如果一个任务先失败,TaskGroup 可能取消尚未执行到抛错位置的其他任务,因此不应把“每个分支一定都会产生一个异常”写进业务假设。处理逻辑应该面向最终收到的异常类型,并为未匹配的异常保留向上抛出的路径。

Python TaskGroup 退出时不同异常类型的处理分流说明图
图2:TaskGroup 退出后的异常处理分流结构图;用于理解关系,不代表运行截图。

取消、业务失败和进程级异常要分开

异常类别TaskGroup 边界上的含义处理重点
CancelledError任务被取消或外部取消正在传播清理资源后继续抛出,不要误报成业务失败
普通异常任务真正失败,多个失败可合并为 ExceptionGroup用 except* 按类型处理,未识别的继续上抛
KeyboardInterrupt / SystemExit进程级退出信号TaskGroup 会等待同组任务收尾,但初始异常仍直接重新抛出

尤其不要在子任务里写一个宽泛的 except Exception 后把所有情况转换成“失败结果”。CancelledError 直接继承自 BaseException,通常不会被这个分支捕获;更重要的是,统一吞错会让调用方无法区分用户主动取消、业务错误和系统退出。

把超时放在任务组的边界上

如果整组任务有一个总时限,可以把 TaskGroup 放进 asyncio.timeout()。超时发生后,任务组内部的子任务会收到取消,外层再根据超时异常做降级。子任务仍然必须在 finally 中释放自己的资源。

import asyncio


async def load_dashboard() -> None:
    try:
        # timeout 约束整组工作,不只约束某一次 sleep。
        async with asyncio.timeout(1.0):
            async with asyncio.TaskGroup() as group:
                group.create_task(load_part("配置", 0.2))
                group.create_task(load_part("库存", 0.8))
                group.create_task(load_part("价格", 0.9))
    except TimeoutError:
        # 总时限耗尽后返回缓存、降级数据或明确的超时结果。
        print("整组任务超时,进入降级路径")


asyncio.run(load_dashboard())

边界要注意两点:第一,超时是外层控制流的结果,不应在每个子任务里重复把取消改写成普通错误;第二,finally 里的清理要短小且可中断,避免清理逻辑本身把 TaskGroup 的退出拖成新的问题。

排查 TaskGroup 没有按预期收敛

  • 确认任务是在 async with TaskGroup() 进入后通过 create_task() 加入的,而不是提前创建后遗留在外部。
  • 检查子任务是否捕获了 CancelledError 却没有重新抛出,或在取消后继续执行长时间不可中断的工作。
  • 检查调用方是否只处理了一个异常类型,导致异常组中未匹配的成员继续向上抛出。
  • 确认超时的位置:总时限放在任务组外层,单个服务的时限才放在对应子任务内部。
  • 不要把静态说明图或日志中的“取消”当成成功;真正的成功应由任务返回值和业务结果决定。

常见问题

TaskGroup 会等待所有子任务自然结束吗?

不一定。没有异常时,它会等待所有任务完成;出现首个非取消异常后,会取消同组剩余任务并等待它们完成取消收尾。

为什么没有在 ExceptionGroup 里看到 CancelledError?

TaskGroup 会把正常的任务取消视为收尾信号,而不是需要集中报告的业务失败。真正需要处理的是非取消异常。

可以用普通 except 捕获 TaskGroup 的异常吗?

可以捕获整个 ExceptionGroup,但如果要按子异常类型分别处理,更适合使用 except*。未被处理的成员仍会继续传播。

子任务里应该返回错误对象而不是抛异常吗?

只有当“单个失败也允许整组成功”是明确业务规则时才返回错误对象。若任一任务失败就应终止这次操作,直接抛出异常才能让 TaskGroup 的结构化取消发挥作用。

总结一下:用 TaskGroup 表达同一业务边界,用子任务的 finally 负责可重复的清理,用 except* 在边界一次性拆分失败。只要不吞掉取消信号,子任务失败就能从“各自散落的 await”收敛为可处理的异常组。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Spring Boot Actuator configprops 怎样确认配置对象的最终值Spring Boot Actuator configprops 怎样确认配置对象的最终值
上一篇
Spring Boot Actuator configprops 怎样确认配置对象的最终值
Python os.splice 怎样在文件描述符之间传输数据
下一篇
Python os.splice 怎样在文件描述符之间传输数据
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    986次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    991次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1113次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1083次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    929次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码