Python 3.12 引入的 asyncio.TaskGroup 比 asyncio.gather() 更安全,因其确保所有任务完成或彻底取消后再退出,避免残留协程和资源泄漏;它不支持 return_exceptions 等参数,异常需在子协程内处理。

Python 3.12 正式引入 asyncio.TaskGroup,它比手动管理 create_task() + await asyncio.gather() 更安全、更简洁,尤其能自动处理子任务异常传播和取消联动。
为什么不能用 asyncio.gather() 替代 TaskGroup
asyncio.gather() 在任一子任务抛出未捕获异常时会立即取消其余任务,但**不会等待它们真正结束**——残留的协程可能继续运行、资源未释放、甚至引发 RuntimeError;而 TaskGroup 保证所有已启动任务都完成(或被正确取消并清理)后再退出上下文。
- 常见错误现象:
RuntimeError: Task was destroyed but it is pending!或日志中看到“coroutine ignored exception” - 使用场景:需并发请求多个 API、同时写入多个文件、批量处理数据库记录等需要强生命周期控制的场景
- 性能影响:两者调度开销接近,但
TaskGroup避免了因异常导致的资源泄漏,长期运行服务中更稳健
TaskGroup 的基本用法和参数差异
它只提供一个方法 create_task(),不接受 timeout、return_exceptions 等 gather() 支持的参数——这些逻辑应由你自己的协程内部处理。
-
TaskGroup没有return_exceptions=True选项:任何未处理异常都会立刻终止整个组,且异常类型保持原样(不是包装成ExceptionGroup) - 若需容错,应在子协程内用
try/except捕获并返回结果,或显式调用task_group.cancel() - 不支持传入 loop 参数(Python 3.12+ 默认使用当前运行循环,
get_running_loop())
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>"))
print(task1.result(), task2.result()) # 安全获取结果</p>如何处理子任务中的异常而不中断整个组
直接让子任务抛异常会终止 TaskGroup 上下文。真要“部分失败仍继续”,必须把异常逻辑收进子协程内部。
立即学习“Python免费学习笔记(深入)”;
- 不要在
tg.create_task()外层 try/catch —— 这无法阻止组退出 - 正确做法:每个子协程自己处理异常,返回 (success, result_or_error) 元组或使用
Result类封装 - 如果某个任务需主动取消其他任务,可调用
tg.cancel(),但注意这会触发所有未完成任务的CancelledError
async def safe_fetch(url):
try:
await asyncio.sleep(0.1)
return ("ok", f"got {url}")
except Exception as e:
return ("error", str(e))
<p>async def main():
results = []
async with asyncio.TaskGroup() as tg:
tasks = [
tg.create_task(safe_fetch("<a href="https://www.php.cn/link/e75a2d435455f0626ccf0af67216e76f">https://www.php.cn/link/e75a2d435455f0626ccf0af67216e76f</a>")),
tg.create_task(safe_fetch("<a href="https://www.php.cn/link/88b3febc5798a734026c82c1012408f5">https://www.php.cn/link/88b3febc5798a734026c82c1012408f5</a>")),
]
for t in tasks:
results.append(await t)</p>与 asyncio.timeout() 配合使用的注意事项
TaskGroup 本身不支持超时,但可嵌套使用 asyncio.timeout() 控制单个子任务——注意超时异常(TimeoutError)会被正常传播并终止整个组。
- 若只想让超时任务失败而不影响其他任务,必须在子协程里用
asyncio.timeout()并捕获TimeoutError - 不要对整个
async with TaskGroup()块加asyncio.timeout()—— 这会导致组退出时部分任务仍在运行,违反设计契约 - 兼容性:
asyncio.timeout()是 Python 3.11+ 引入,3.12 中与TaskGroup协同稳定
复杂点在于异常边界必须清晰:哪个层级负责捕获、哪个层级负责传播、哪个层级负责清理——TaskGroup 只管“一起生一起死”,具体策略得你自己定。


















