当前位置:首页 > 文章列表 > 文章 > python教程 > Python asyncio TaskGroup 一个任务失败时其他任务怎么收尾

Python asyncio TaskGroup 一个任务失败时其他任务怎么收尾

来源:17golang原创 2026-09-08 00:47:02 0浏览 收藏

asyncio.TaskGroup 并发跑多个任务时,最容易误判的一点是:一个任务抛出普通异常,其他任务不会继续“各跑各的”。TaskGroup 会取消仍在运行的兄弟任务,等待它们完成清理,再把非取消异常组合成 ExceptionGroup 抛出。正确的收尾方式是把资源释放放进 finally,清理结束后不要吞掉 CancelledError

排查这类问题时,先确认失败来源,再确认兄弟任务是否进入 finally,最后才处理 ExceptionGroup。超时本质上也是取消当前任务和子任务,不能把 TimeoutError 当成每个子任务都会单独抛出的错误。
要点速览
  • 首个非 CancelledError 的任务异常会触发 TaskGroup 取消剩余任务。
  • 子任务必须在 finally 释放连接、文件或临时状态,取消清理完成后继续抛出取消信号。
  • except* 处理的是异常组的匹配子集;超时和外部取消要在更外层判断。

为什么一个任务失败后其他任务也会停

TaskGroup 是结构化并发的边界。下面的例子让快速任务失败,让慢任务停在等待处:

import asyncio

async def worker(name: str, delay: float, fail: bool = False) -> None:
    try:
        await asyncio.sleep(delay)
        if fail:
            raise RuntimeError(f"{name} 读取上游失败")
        print(f"{name} 完成")
    finally:
        # 无论成功、失败还是取消,都在这里释放资源
        print(f"{name} 执行清理")

async def main() -> None:
    async with asyncio.TaskGroup() as group:
        group.create_task(worker("fast", 0.1, fail=True))
        group.create_task(worker("slow", 5))

asyncio.run(main())

fast 抛出 RuntimeError 后,slow 会收到取消请求并执行 finally。上下文管理器退出时会等待组内任务结束,因此不能把离开 async with 理解成“立即强杀”。如果清理代码本身又等待网络或锁,退出时间也会随之变长。

Python asyncio TaskGroup 中失败任务、兄弟任务取消与 finally 清理的静态关系图
图1:TaskGroup 边界内,普通异常连接到兄弟任务的取消请求,所有任务都要经过清理边界。

CancelledError 要清理,但不要吞掉

取消不是业务失败,它是 asyncio 用来通知协程停止工作的控制信号。官方文档建议用 try/finally 做可靠清理;如果显式捕获 CancelledError,清理完成后通常应继续抛出。

async def fetch_with_session(session, url: str) -> bytes:
    try:
        # 业务代码可能在这里被 TaskGroup 或 timeout 取消
        return await session.get_bytes(url)
    except asyncio.CancelledError:
        # 只做轻量记录或释放动作,不把取消改写成成功
        print(f"取消请求:{url}")
        raise
    finally:
        # 关闭本协程独占的临时资源;共享连接池不要在这里误关
        await session.release_request(url)

最危险的写法是 except asyncio.CancelledError: return None。它会让上层以为任务正常结束,还可能干扰 TaskGroup 或超时上下文内部依赖的取消机制。只有确实要抑制取消时,才需要同时理解任务的取消状态,并承担改变控制流的后果。

用 ExceptionGroup 找到真正失败的任务

TaskGroup 退出后,多个非取消异常会组合起来。普通的 except RuntimeError 不能替代对异常组的处理,应使用 except* 按类型匹配:

async def run_batch() -> None:
    try:
        async with asyncio.TaskGroup() as group:
            group.create_task(load_profile())
            group.create_task(load_orders())
    except* (TimeoutError, ConnectionError) as errors:
        # 这里只处理可重试的网络类错误,其余异常继续向上
        for error in errors.exceptions:
            print(f"可重试异常:{error!r}")
    except* ValueError as errors:
        # 数据格式错误通常应记录并修复输入,不要盲目重试
        print(f"输入数据异常数量:{len(errors.exceptions)}")

except* 的每个分支拿到的是匹配后的异常组,不保证只有一个叶子异常。不要在分支里把所有错误都标成成功;未匹配部分会继续传播,正好能保留真正的故障证据。

asyncio timeout、TaskGroup、外部取消与 ExceptionGroup 的静态边界关系图
图2:超时和外部取消从组外进入,子任务通过 CancelledError 收到信号,业务异常再由 except* 分类处理。

asyncio.timeout 应该放在哪一层

如果一批任务共享一个总时限,把 asyncio.timeout 放在 TaskGroup 外层最容易表达意图:总时限到达时,当前任务被取消,TaskGroup 负责等待子任务收尾,外层再得到 TimeoutError

async def run_with_deadline() -> None:
    try:
        async with asyncio.timeout(2):
            async with asyncio.TaskGroup() as group:
                group.create_task(load_profile())
                group.create_task(load_orders())
    except TimeoutError:
        # 统一处理这一批超过总时限的结果
        print("批处理超时,检查慢任务和清理耗时")

若每个任务有自己的预算,可以在 worker 内部使用独立 timeout,但要明确它产生的是局部超时,是否让异常离开 worker 触发整组取消。排查时建议记录三类证据:哪个任务先抛出普通异常、哪些任务执行了 finally、最外层最终捕获的是 TimeoutError 还是 ExceptionGroup。

发布前可以照着做的收尾清单

现象先检查正确判断
兄弟任务突然结束是否有首个非取消异常TaskGroup 的故障联动取消生效
程序迟迟不退出finally 是否还在等待 I/O取消不是强杀,清理耗时会进入退出时间
日志只剩一条大异常是否使用 except*按异常类型拆出 ExceptionGroup 的匹配子集
超时后状态不一致是否吞掉 CancelledError清理后重新抛出取消信号

相关问题

TaskGroup 会取消已经完成的任务吗?

不会。已经完成的任务不会再次执行取消流程,只有仍在运行的兄弟任务会收到取消请求。

为什么 finally 执行了,TaskGroup 还没退出?

TaskGroup 要等待任务真正结束。finally 中如果还有异步清理、锁竞争或网络等待,退出就会继续等待。

可以用 gather 替代 TaskGroup 吗?

两者语义不同。需要明确的父子任务边界、失败联动取消和异常组时优先考虑 TaskGroup;迁移前要按失败、取消和超时分别回归。

TaskGroup 的关键不是“失败后全部停止”,而是把失败传播、兄弟取消和资源清理放进同一个可观察边界。只要保留取消信号,按异常组分类,超时放在清晰的层级,收尾行为就能稳定复查。完整语义可继续核对 Python asyncio 任务文档PEP 654

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go mutex profile 没有热点时怎么确认采集开关Go mutex profile 没有热点时怎么确认采集开关
上一篇
Go mutex profile 没有热点时怎么确认采集开关
Go big.Rat 转小数时怎么避免提前丢失精度
下一篇
Go big.Rat 转小数时怎么避免提前丢失精度
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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测试功能,助您快速选择最适合项目的高性能大语言模型。
    5次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    174次使用
  • C-Eval中文评测基准:大语言模型多学科能力评估指南
    C-Eval
    深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
    106次使用
  • AI Prompt Library:免费AI提示词库,助力ChatGPT高效创作与营销
    AI Prompt Library
    探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
    34次使用
  • LangGPT提示词框架:结构化Prompt设计方法与开源工具指南
    LangGPT
    LangGPT是一种受编程语言启发的结构化提示词设计工具,提供双层框架、模块化模板及变量功能,帮助用户高效编写高质量Prompt。该项目已在GitHub免费开源,适用于内容创作、编程辅助等多场景。
    43次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码