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

TaskGroup 是什么,为什么不用 asyncio.gather?
TaskGroup 是 Python 3.11 引入的正式异步并发控制机制,用来替代过去容易出错的手动 asyncio.create_task + await asyncio.wait 模式。它和 asyncio.gather 的核心区别在于:前者支持「任意子任务失败时自动取消其余正在运行的任务」,后者默认会等所有任务完成(哪怕某个已抛异常),除非显式传 return_exceptions=True —— 这在爬虫场景下很危险:一个请求超时或解析崩溃,不该让其他几十个还在跑的 HTTP 请求继续占着连接池、耗着资源。
如何用 TaskGroup 启动一批并发爬虫任务?
典型场景:你有一组 URL,想并发请求并解析 HTML,每个请求封装成独立协程。关键点是别把 await 写在 async with asyncio.TaskGroup() 块里——那会变成串行;所有任务必须通过 tg.create_task() 提交:
import asyncio
import aiohttp
<p>async def fetch_url(session, url):
try:
async with session.get(url, timeout=5) as resp:
return await resp.text()
except Exception as e:
return f"ERROR: {e}"</p><p>async def main():
urls = ["<a href="https://www.php.cn/link/5f69e19efaba426d62faeab93c308f5c">https://www.php.cn/link/5f69e19efaba426d62faeab93c308f5c</a>", "<a href="https://www.php.cn/link/ef246753a70fce661e16668898810624">https://www.php.cn/link/ef246753a70fce661e16668898810624</a>", "<a href="https://www.php.cn/link/874b2add857bd9bcc60635a51eb2b697">https://www.php.cn/link/874b2add857bd9bcc60635a51eb2b697</a>"]
async with aiohttp.ClientSession() as session:
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(fetch_url(session, url)) for url in urls]</p><h1>此处才 await 所有结果(自动等待全部完成或任一失败)</h1><pre class="brush:php;toolbar:false;"> results = [t.result() for t in tasks]
print(results)
注意:tg.create_task() 立即返回 asyncio.Task 对象,不阻塞;async with 块退出时自动 await 所有未完成任务,并聚合异常。
TaskGroup 下如何处理单个子任务失败?
TaskGroup 默认行为是「任一子任务抛出未捕获异常,整个 group 立即取消其余任务,并把该异常原样 re-raise」。这很适合爬虫——比如某个域名 DNS 解析失败,没必要再等其他请求超时。但如果你希望某些错误被忽略(如个别 URL 404),必须在子协程内部 try/except 捕获并返回兜底值,而不是让它冒泡:
Python 3.14.2是Python编程语言在2025年12月5日发布的稳定版本,属于3.14系列的第二个维护更新。该版本包含了18项修复,重点解决了多进程、数据类及正则表达式等模块的回归问题,并修复了CVE-2025-12084等安全漏洞。此版本标志着自由线程模式(移除GIL)正式获得官方支持,是Python发展的重要里程碑。
- ❌ 错误写法:
async def fetch_url(...): raise aiohttp.ClientError(...)→ 整个 TaskGroup 中断 - ✅ 正确写法:
async def fetch_url(...): try: ... except aiohttp.ClientError: return None - 若需区分失败类型,可在返回值中带标记,比如
{"status": "error", "reason": "timeout"}
不要依赖 TaskGroup 的 exceptions 属性做后处理——它只在 group 退出后才可读,且仅包含未被捕获的异常,对已吞掉的错误无感知。
与 aiohttp.ClientSession 配合时的常见坑
最大陷阱是把 ClientSession 创建放在 TaskGroup 内部,导致每个子任务新建 session,连接池失效、DNS 缓存丢失、甚至触发连接数爆炸:
- ❌ 错误:
async with asyncio.TaskGroup() as tg:→async with aiohttp.ClientSession() as s:→tg.create_task(fetch(s, url)) - ✅ 正确:session 必须在 TaskGroup 外创建并传入,确保复用底层 TCP 连接池
- 另一个坑:忘记给
aiohttp.ClientSession加timeout参数,TaskGroup 不会替你限制单个请求耗时,超时任务会一直挂着直到手动 cancel
TaskGroup 本身不管理网络层细节,它只管任务生命周期。爬虫健壮性取决于你在子协程里怎么设 timeout、重试、错误分类——这些都得自己写清楚。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










