TaskGroup 是 Python 3.11 引入的结构化并发工具,确保子任务共生死、异常原样抛出且支持多异常分类捕获;而 asyncio.gather 不自动取消已启动任务、异常信息模糊且不支持按类型分别处理。

TaskGroup 是什么,和 asyncio.gather 有什么区别
TaskGroup 是 Python 3.11 引入的正式语法糖,用来替代手写 asyncio.create_task + try/finally 或 asyncio.gather 的繁琐异常传播逻辑。它不是新调度器,也不改变事件循环行为,而是提供一种「结构化并发(structured concurrency)」的语义:所有子任务共生死,任一出错即取消其余,且错误原样抛出,不被吞掉。
对比 asyncio.gather(return_exceptions=False)(默认):
-
gather遇到第一个异常就中止,但已启动的其他协程不会被取消(可能继续运行、产生副作用) -
gather抛出的是ExceptionGroup(3.11+)或单个异常(旧版),但无法区分哪个子任务失败,也难做针对性清理 -
TaskGroup内所有任务共享生命周期,取消信号会传播到底层协程,且支持在except块中按类型捕获不同子任务的异常
怎么用 TaskGroup 启动并等待多个异步任务
核心是用 async with asyncio.TaskGroup() as tg: 语法,然后调用 tg.create_task() 提交协程。所有任务在 with 块退出时自动 await 完成或被取消。
示例:同时请求三个 API,任一超时或报错,其余立即取消
import asyncio
import aiohttp
<p>async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url, timeout=2) as resp:
return await resp.text()</p><p>async def main():
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(fetch("<a href="https://www.php.cn/link/5f69e19efaba426d62faeab93c308f5c">https://www.php.cn/link/5f69e19efaba426d62faeab93c308f5c</a>"))
task2 = tg.create_task(fetch("<a href="https://www.php.cn/link/85c19375f0c12c6793bf66b4e2666dc4">https://www.php.cn/link/85c19375f0c12c6793bf66b4e2666dc4</a>")) # 超时
task3 = tg.create_task(fetch("<a href="https://www.php.cn/link/874b2add857bd9bcc60635a51eb2b697">https://www.php.cn/link/874b2add857bd9bcc60635a51eb2b697</a>"))</p><pre class="brush:php;toolbar:false;"># 所有 task 已完成或被取消,task1.result() 可取,task2/task3 抛异常
print(task1.result()[:50])
注意:tg.create_task() 返回的是 asyncio.Task 对象,可随时读取 .done()、.cancelled() 状态,但不要手动 await 它——TaskGroup 会统一处理。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
TaskGroup 如何处理多个异常同时发生
当多个子任务各自抛出不同异常(比如一个 TimeoutError,一个 aiohttp.ClientError),TaskGroup 会把它们打包进一个 ExceptionGroup 抛出,而不是只报第一个。这是它相比 gather 最关键的升级点。
你可以用标准库的 exceptiongroup 模块(Python 3.11+ 内置)来拆解:
try:
async with asyncio.TaskGroup() as tg:
tg.create_task(crash_with_timeout())
tg.create_task(crash_with_value_error())
except* TimeoutError as eg:
print("超时任务数:", len(eg.exceptions))
except* ValueError as eg:
print("值错误任务数:", len(eg.exceptions))
except ExceptionGroup as eg:
print("其他未分类异常:", eg)
重点:except* 是 Python 3.11 新语法,专为匹配 ExceptionGroup 中的子异常设计;普通 except 只能捕获整个 ExceptionGroup 对象。
常见踩坑点:取消行为、作用域和兼容性
TaskGroup 不是万能胶,几个容易忽略的细节直接导致逻辑错乱:
- 子任务里如果用了
asyncio.shield(),会被取消信号绕过——TaskGroup发起的取消对 shielded 协程无效 -
tg.create_task()必须在async with块内调用;若提前保存 task 对象、等块外再 await,会抛RuntimeError: TaskGroup is closed - 不能在
TaskGroup内部再嵌套另一个TaskGroup并期望父组控制子组生命周期——子组有自己的取消链,需显式传递 cancellation token - Python TaskGroup,回退方案只能用
asyncio.gather+return_exceptions=True,再手动遍历结果检查isinstance(..., BaseException)
最易被忽略的是:TaskGroup 的「结构化」约束只作用于它直接创建的任务,不穿透到子协程内部 spawn 的任何后台任务(比如用 asyncio.create_task 在子协程里另起的 task)。那些 task 会脱离管控,变成真正的“孤儿”。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










