taskgroup 是结构化并发的语义替代而非 gather 增强版,强制共生死、不吞异常、协作取消;必须用 async with 创建,仅限块内用 tg.create_task() 提交任务,异常聚合为 exceptiongroup 需 except* 捕获,不保证结果顺序。

TaskGroup 不是 gather 的增强版,而是结构化并发的语义替代——它强制任务共生死、异常不吞、取消可协作,而 gather 在生产环境里容易漏清理、难调试、行为不一致。
asyncio.TaskGroup 必须用 async with,否则 RuntimeError: TaskGroup is not running
这是最常卡住人的第一步。你不能写 tg = asyncio.TaskGroup(),也不能在函数外提前创建再传入协程。TaskGroup 的生命周期完全绑定在 async with 块内:
- 离开
async with asyncio.TaskGroup() as tg:块时,框架自动调用__aexit__,等待所有子任务完成或响应取消 - 块内必须用
tg.create_task()提交任务;用asyncio.create_task()或直接 await 协程,TaskGroup 完全不感知,也无法管理 - 把
tg存到类属性、全局变量或跨协程传递,一旦原上下文退出,再调用tg.create_task()就会报RuntimeError: TaskGroup is closed
异常发生时,TaskGroup 自动取消其余任务,gather 默认不取消
这是核心差异,不是“功能多一点”,而是资源安全的分水岭。比如并发请求 5 个 API,第 3 个抛出 TimeoutError:
安全 Python 代码沙箱:在隔离的沙箱中执行任意 Python 代码,预装 requests、NumPy、pandas 库。返回 stdout、stderr。
-
asyncio.gather()(默认):只抛出第一个异常,第 4、5 个仍在运行,可能持续发包、占连接、写临时文件,你根本不知道它们是否还在跑 -
asyncio.gather(return_exceptions=True):返回列表含Exception实例,但你要自己遍历判断、手动 cancel 其余 task,漏一个就埋泄漏 -
asyncio.TaskGroup:立刻向所有活跃子任务发送CancelledError,并等待它们执行完async with或try/finally中的清理逻辑(如关闭 aiohttp.ClientSession),再聚合异常抛出
异常类型是 ExceptionGroup,必须用 except* 捕获,不能用普通 except
多个子任务同时失败时,TaskGroup 把它们打包成 ExceptionGroup 抛出,这不是语法糖,是类型强制:
- 写
except RuntimeError:或except Exception:捕不到 ——ExceptionGroup是BaseException子类,但不是Exception子类 - 正确捕获方式是
except* RuntimeError:(注意星号),它能匹配组内任意一个RuntimeError实例 - 如果想分别处理不同错误,可以用多个
except*块,比如先except* TimeoutError,再except* ConnectionError - 别试图用
str(e)或repr(e)打印后人工解析 ——ExceptionGroup的exceptions属性才是可靠入口
TaskGroup 不保证结果顺序,别依赖索引对应关系
和 gather 返回按参数顺序排列的结果列表不同,TaskGroup 没有“结果数组”的概念:
- 你无法通过
results[0]知道它对应哪个子任务 —— 因为根本没有results变量;每个任务要自己保存结果,比如写进字典或列表 - 常见做法是在提交时就绑定标识:
tg.create_task(fetch(url, name="user_123")),然后在协程内部把结果存到共享结构中 - 如果真需要顺序,得自己加序号参数或用
asyncio.as_completed()配合 TaskGroup,但后者会破坏“共生死”语义,慎用
真正难的不是写对语法,而是把“任务必须受管”这个约束刻进直觉——只要出现未被 tg.create_task() 启动的协程、或把 tg 流出作用域、或用普通 except 捕异常,TaskGroup 就退化成 gather,结构化并发的保障就没了。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










