TaskGroup不能用gather替代,因其提供结构化并发:强制async with边界、任一失败即协作取消全部任务、等待清理完成再退出、抛ExceptionGroup需except*捕获、支持动态创建任务且杜绝孤儿任务。

Python 3.11 原生支持 asyncio.TaskGroup,它比手动 asyncio.create_task() + await asyncio.gather() 更安全、更可控——尤其在异常传播和资源清理上。
TaskGroup 为什么不能用 asyncio.gather 替代?
asyncio.gather() 在任一子任务抛出异常时会立即取消其余任务,但不会等它们真正结束;而 TaskGroup 保证所有已启动任务完成(或被取消后彻底退出)才退出上下文,避免资源泄漏或状态不一致。
- 如果某个任务在
gather()中崩溃,其他任务可能还在运行却被强制中断,导致__aexit__未执行、文件未关闭、连接未释放 -
TaskGroup内部使用结构化并发模型:一个任务失败,会「通知」其他任务尽快退出,并等待它们完成清理逻辑 -
gather(return_exceptions=True)虽能捕获异常,但无法区分“哪个任务失败”且不提供统一的取消协调机制
如何正确创建并运行 TaskGroup?
必须用 async with asyncio.TaskGroup() as tg: 语法,不能直接实例化或手动调用 create_task() 方法。
-
tg.create_task()是唯一合法的提交方式,返回的是asyncio.Task对象,但生命周期由TaskGroup管理 - 不能在
with块外 await 这些 task,否则会报RuntimeError: Task has not been scheduled - 若在
with块内未 await 任何 task,TaskGroup会静默退出(不报错),但所有已提交任务仍会并发执行
示例:
import asyncio
<p>async def fetch(url):
await asyncio.sleep(0.1)
return f"done {url}"</p><p>async def main():
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(fetch("<a href="https://www.php.cn/link/e75a2d435455f0626ccf0af67216e76f">https://www.php.cn/link/e75a2d435455f0626ccf0af67216e76f</a>"))
task2 = tg.create_task(fetch("<a href="https://www.php.cn/link/88b3febc5798a734026c82c1012408f5">https://www.php.cn/link/88b3febc5798a734026c82c1012408f5</a>"))</p><h1>不需要 await task1/task2 —— TaskGroup 自动等待全部完成</h1><pre class="brush:php;toolbar:false;">print(task1.result(), task2.result()) # 此时才可安全调用 .result()asyncio.run(main())
TaskGroup 遇到异常时怎么传播?
只要有一个任务 raise 异常,TaskGroup 会在 with 块退出时把第一个非 BaseException 的异常重新抛出,其余任务的异常会被压制(除非是 KeyboardInterrupt 或 SystemExit)。
- 多个任务同时失败时,只暴露第一个被检测到的异常(按完成顺序,非提交顺序)
- 被取消的任务若在清理中又 raise 新异常,该异常会被忽略,不影响主异常传播
- 若想捕获所有异常,需在每个任务内部处理,或改用
asyncio.create_task()+asyncio.wait(..., return_when=asyncio.FIRST_EXCEPTION)手动控制
TaskGroup 和 asyncio.create_task() 混用会怎样?
绝对不要混用。单独用 asyncio.create_task() 提交的任务不受 TaskGroup 管理,即使它在 with 块内运行,也不会被自动等待或取消。
- 这种任务可能在
TaskGroup退出后继续运行,造成“幽灵任务”问题 -
TaskGroup不感知这些任务,所以不会等它们结束,也不会向它们发送取消信号 - 若你确实需要混合调度(比如部分任务要独立于 group 生命周期),应明确分离作用域:用不同 event loop scope 或显式
await task
容易被忽略的一点:TaskGroup 的 cancel 信号是通过 task.cancel() 发送的,这意味着任务必须响应 CancelledError 并做清理;如果任务里写了 except BaseException: 吞掉所有异常,就可能卡住整个 group 退出。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











