Python asyncio.TaskGroup 怎样在任务组结束时集中处理异常
来源:17golang原创
时间:2026-10-26 04:01:45 256浏览 收藏
将多个协程用TaskGroup.create_task加入任务组,在async with外层用except*捕获指定类型的异常子组。某个任务出现非取消异常后,TaskGroup会取消其余任务并等待它们结束,然后把已发生的非取消异常集中抛出;它不会为了收集所有业务错误而放任其余任务继续完成。
官方地址:https://www.python.org/
准备 Python 3.11 及以上环境
设想一个同时读取订单摘要和库存摘要的异步入口。如果由我维护这个入口,我会先明确:任何一项失败就让本次整体请求失败,还是允许各项独立失败。这里采用前一种约定,适合用TaskGroup。下面是为说明机制设计的本地模拟,未连接真实业务服务,也未在本机执行。
TaskGroup和except*从Python 3.11开始提供。保存示例为taskgroup_demo.py,使用Python 3.11及以上解释器,无需安装第三方库。不要把except*代码交给Python 3.10解析。

把任务与异常出口放在同一个作用域
把try放在async with外面才能覆盖退出任务组时抛出的异常。示例让订单任务抛出ValueError,库存任务进入可取消的等待;finally代表资源清理的位置。
import asyncio
async def load_summary(name, delay, invalid=False):
try:
await asyncio.sleep(delay) # 模拟可取消的异步等待
if invalid:
raise ValueError(f"{name}: 摘要格式错误")
return {"name": name, "items": 3}
finally:
# 实际项目在此关闭本任务持有的资源。
print(f"{name}: 清理结束")
def leaves(group):
# 子组可以继续嵌套,递归取得叶子异常。
for error in group.exceptions:
if isinstance(error, BaseExceptionGroup):
yield from leaves(error)
else:
yield error
async def main():
tasks = {}
try:
async with asyncio.TaskGroup() as group:
tasks["订单"] = group.create_task(
load_summary("订单", 0.02, invalid=True), name="orders"
)
tasks["库存"] = group.create_task(
load_summary("库存", 1.0), name="inventory"
)
except* ValueError as errors:
for error in leaves(errors):
print(f"输入问题: {error}")
else:
# 仅在任务组正常完成时读取全部结果。
print({key: task.result() for key, task in tasks.items()})
if __name__ == "__main__":
asyncio.run(main())
except* ValueError匹配异常组中的ValueError叶子,并保留子组结构,所以errors.exceptions不保证是扁平列表。辅助函数leaves只做遍历,便于逐条记录。本例没有用except* Exception吞掉所有未知问题。
任务组正常完成后才能直接按成功路径读取结果;若已经处理过异常,不能随后无条件对全部任务调用result(),因为其中可能有失败或被取消的任务。
运行时观察取消与异常分组
读者可运行以下命令:
python3 taskgroup_demo.py预期会看到任务清理消息和“输入问题”消息,而不会打印整批成功结果。输出行的具体顺序与调度有关,不应据此推导任务执行顺序;这里提供的是阅读判断标准,没有提供伪造的实测输出。
| 发生的情况 | 组退出时的含义 |
|---|---|
| 所有任务成功 | 正常离开async with,可读取结果 |
| 一个任务抛ValueError | 其余未完成任务被取消,等待退出后抛异常组 |
| 多个任务在取消生效前已经失败 | 已发生的非取消异常可共同进入异常组 |
| 任务因组内取消而结束 | CancelledError通常不作为普通失败加入异常组 |
集中处理并不保证收集到每个任务原本可能产生的错误。库存任务可能在到达自己的业务检查之前就已取消。若需求是每项都独立完成并形成逐项结果,应在任务内部把预期业务失败转换为明确结果对象,而不是误用TaskGroup的失败即取消语义。

扩展实验:保留未知异常和部分结果
可以把订单任务的ValueError改成RuntimeError,理解未知异常会继续向上传播。若组内同时存在ValueError与其他未匹配异常,except*只处理匹配部分,剩余部分仍会抛出。不要用裸except把外层请求伪装成成功。
另一个实验是给多个任务设置相近延迟并让它们分别失败。实际能收集多少个异常取决于失败与取消的时机,不能依赖固定数量或固定排序,也不应根据exceptions列表的位置反推任务身份。业务异常消息、附加字段或任务侧日志应携带自己的业务标识。
如果确实要保留部分成功结果,在处理完异常之后逐个判断task.cancelled(),再检查task.exception(),最后才读取task.result()。此时TaskGroup已经等待组内任务结束,判断应发生在组退出之后。部分成功如何对外返回仍是业务接口约定,不能靠捕获异常自动决定。
TaskGroup只管理通过该组create_task创建的任务。若子协程另用asyncio.create_task生成游离任务,不能假设它也会被当前组完整管理。
退出任务组后的清理与常见问题
资源释放放进finally或异步上下文管理器。若显式捕获asyncio.CancelledError做清理,通常应在清理后重新raise;吞掉取消可能干扰TaskGroup的结构化并发行为。CancelledError继承BaseException,普通except Exception不会捕获它。
为什么只有一个失败也会进入except*?
TaskGroup退出时可以把单个普通失败包装成异常组;except*面对的不是“必须至少两个错误”的特殊情况。
能否在同一个try中同时写except和except*?
不能。同一try的处理分支不能混用两种语法,需要嵌套try安排不同边界。except*分支内部也不能使用return、break或continue。
KeyboardInterrupt也会变成普通异常组吗?
KeyboardInterrupt和SystemExit有特殊规则:任务组会取消并等待其他任务,然后重新抛出原始异常。外部取消也不是应当随手忽略的业务失败。
async with内部主体抛错怎么办?
组主体的非取消异常也会触发取消与等待,并可能与任务异常一起被分组。把捕获边界放在整个async with外侧可以覆盖这一出口。
本文只用本地协程与标准库,实验结束后无需清理外部服务。真正接入网络客户端时,每个客户端、连接或事务仍应有明确的释放与失败语义。
-
346 收藏
-
235 收藏
-
387 收藏
-
447 收藏
-
360 收藏
-
276 收藏
-
473 收藏
-
488 收藏
-
461 收藏
-
257 收藏
-
419 收藏
-
115 收藏
-
378 收藏
-
文章 · python教程 | 1小时前 | TLS · python · 网络安全 · Python ssl.SSLContext TLS版本 minimum_version maximum_version399 收藏
-
353 收藏
-
137 收藏
-
446 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习