TaskGroup不是并发加速器,而是防止资源泄漏和失控任务的安全护栏;它强制统一子任务生命周期与错误命运,通过async with确保自动等待、取消传播和异常聚合。

TaskGroup 不是“更高效”的并发加速器,而是防止隐性资源泄漏和失控任务的安全护栏。它不加快单个协程执行,但能避免后台任务偷偷吃 CPU、连接、文件句柄——这才是真实场景下的“高效”。
为什么不能直接用 asyncio.create_task() 堆列表?
常见错误现象:循环里反复调用 asyncio.create_task(),只保留最后一个变量,前面的任务引用丢失 → 它们还在跑 HTTP 请求或重试,但你无法 await、无法 cancel。
- 任务脱离作用域后,若没显式
await task或task.cancel(),就变成僵尸任务 -
asyncio.gather(*tasks)遇到第一个异常会中止,但其余任务继续运行(可能发第5次重试、解码大 JSON、写临时文件) - 无法在运行时动态增删任务:比如根据 A 任务响应决定是否启动 B/C
- 资源清理靠人肉
try/finally,漏写一行就泄漏连接或锁
async with asyncio.TaskGroup() 必须这么写
这不是建议,是硬约束。错写成 tg = asyncio.TaskGroup() 或手动调 __aenter__,TaskGroup 就不会自动等待子任务,也不会传播取消信号。
- ✅ 正确:
async with asyncio.TaskGroup() as tg: - ❌ 错误:
tg = asyncio.TaskGroup()、await tg.__aenter__()、tg = await asyncio.TaskGroup().__aenter__() - 离开
async with块时,无论正常退出还是异常中断,框架都会自动await所有已创建但未完成的任务 - 所有通过
tg.create_task()启动的任务,共享同一个取消信号源;任一抛异常,其余立刻收到CancelledError
怎么捕获多个子任务各自的异常?
TaskGroup 抛出的是 ExceptionGroup,普通 except Exception: 会完全漏掉——这是最常踩的坑。
立即学习“Python免费学习笔记(深入)”;
- 必须用
except*语法(Python 3.11+ 独有) -
except* ValueError as eg:只捕获其中类型为ValueError的异常分支 -
eg.exceptions是一个元组,可遍历处理每个具体异常 - 若想忽略某类异常(如网络超时),需显式
pass,否则整个ExceptionGroup仍会向上冒泡
TaskGroup 和数据结构不是一回事
有人以为 async with asyncio.TaskGroup() 之后对 list 或 dict 的读写就自动线程/协程安全了——完全错误。
-
TaskGroup只管任务生命周期,不管数据竞争 - 多个子任务往同一个
listappend()?会丢数据、报RuntimeError - 正确做法:用
asyncio.Queue收集结果,或用asyncio.Lock包裹共享写入 - 典型组合:
queue = asyncio.Queue()在async with外创建,所有子任务await queue.put(...),主协程最后while not queue.empty(): await queue.get()(注意:empty()并发下不可靠,建议配合计数器)
真正容易被忽略的点是:取消传播依赖子任务自己配合。如果某个子任务里写了死循环且从不检查 asyncio.current_task().cancelled(),async with 会卡住,直到它自己退出或超时。


















