taskgroup 比 asyncio.gather 更安全而非更高效,它通过可预测开销确保资源不泄露、异常不静默、任务不孤儿,并强制生命周期绑定、异常用 except* 处理、支持动态任务创建。

TaskGroup 不是比 asyncio.gather “更高效”,而是更安全——它用可预测的开销换来了资源不泄露、异常不静默、任务不孤儿。
TaskGroup 的“慢”其实是它在做你原来没做的事
压测时发现 TaskGroup 比 gather 多耗几十毫秒?这不是性能倒退,是它在等子任务真正完成清理:
- HTTP 请求里用了
async with aiohttp.ClientSession()?TaskGroup会等 session 关闭才退出 - 数据库事务用了
async with db.transaction()?它会等 commit 或 rollback 完成 - 子任务里没写
try/finally或忽略task.cancelled()?那多出来的等待就是空等——问题不在TaskGroup,而在协程没响应取消
gather 默认不取消其余任务,第一个异常抛出后,其他任务还在后台跑:连接没关、文件没 flush、锁没释放。你省下的那点时间,可能换来生产环境半夜告警。
异常必须用 except*,老式 except ValueError: 完全失效
TaskGroup 抛出的永远是 ExceptionGroup,哪怕只有一个任务失败。这意味着:
-
except ValueError:一句也捕获不到——因为ValueError被包在ExceptionGroup里了 - 正确写法是
except* ValueError as eg:,其中eg.exceptions是匹配的异常列表 - 多个任务分别抛
TimeoutError和JSONDecodeError?可以并列写except* TimeoutError:和except* JSONDecodeError:,各自处理
而 gather(return_exceptions=True) 把异常塞进结果列表,你得手动遍历每个元素做 isinstance(result, Exception) 判断——漏一个,就埋一个泄漏点。
生命周期强制绑定在 async with 块内
TaskGroup 不是普通类,不能实例化,也不能存到类属性或全局变量里:
- 错写
tg = asyncio.TaskGroup()→ 立刻报RuntimeError: TaskGroup is not running - 把
tg传给另一个协程,再调tg.create_task()→RuntimeError: TaskGroup is closed - 所有任务必须用
tg.create_task()启动;用asyncio.create_task()启动的不会被管理,照样变僵尸任务
这种“不自由”,恰恰堵死了最常见的三类错误:忘记 await、任务变量被覆盖、共享资源生命周期错位(比如多个任务共用一个已 close 的 AsyncClient)。
动态任务支持让逻辑更贴近真实业务
你在 async with 块里可以随时根据前一个请求结果决定是否拉下一页:
async with asyncio.TaskGroup() as tg:
first_page = tg.create_task(fetch_page(1))
await first_page
if first_page.result().has_next:
tg.create_task(fetch_page(2)) # ✅ 合法
tg.create_task(fetch_page(3)) # ✅ 合法
gather 要求所有协程提前列好,没法在运行中动态增减。一旦业务逻辑需要条件分支或循环追加任务,gather 就得套多层 await + 手动 create_task,极易漏清理。
真正难的不是写对语法,而是让每个子任务都响应取消——TaskGroup 把边界划清了,但协程里要不要写 if task.cancelled(): return,还是得你自己负责。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











