asyncio.gather、wait、as_completed 和 TaskGroup 有什么区别?
简化版
gather 适合并发执行一组任务并按输入顺序收集结果;wait 更偏底层,可以等待一批任务达到完成条件;as_completed 适合谁先完成先处理;TaskGroup 提供结构化并发,任务组内有异常时会统一取消和收尾,更适合现代 Python 中管理一组相关任务。
详细版
gather:
results = await asyncio.gather(fetch_a(), fetch_b())
结果顺序和输入顺序一致。
as_completed:
tasks = [asyncio.create_task(fetch(url)) for url in urls]
for task in asyncio.as_completed(tasks):
result = await task
handle(result)
谁先完成先处理。
wait:
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
可以控制等待到第一个完成、全部完成或第一个异常。
TaskGroup:
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(fetch_a())
task2 = tg.create_task(fetch_b())
任务组退出时会等待组内任务完成;如果任务出错,会更系统地处理取消和异常聚合。
面试回答重点:gather 重结果聚合,as_completed 重完成顺序,wait 重等待条件,TaskGroup 重结构化生命周期管理。
完整版教学
一、为什么需要多种并发组合方式
异步任务组合有不同需求:
- 我想拿到所有结果;
- 我想谁先回来先处理;
- 我只等第一个成功结果;
- 任何一个失败就取消全部;
- 我希望一组任务有明确生命周期。
这些需求不同,所以 asyncio 提供了多种工具。
二、gather 适合收集一组结果
gather 是最常见的批量并发工具:
async def main():
user, orders = await asyncio.gather(
get_user(),
get_orders(),
)
它会并发运行这些 awaitable,并把结果按输入顺序返回。即使 get_orders() 先完成,返回列表里仍然在第二个位置。
如果其中一个任务抛异常,默认情况下 gather 会把异常传播给等待者。异常和取消细节要按具体版本与参数理解,工程里要明确是否使用 return_exceptions=True,不要模糊处理。
三、as_completed 适合流式处理结果
如果有 100 个 URL,谁先下载完就先解析,适合:
tasks = [asyncio.create_task(fetch(url)) for url in urls]
for task in asyncio.as_completed(tasks):
try:
result = await task
handle(result)
except Exception as exc:
log_error(exc)
它的优势是降低尾部慢任务对整体处理的影响。搜索聚合、批量请求、并发爬取里经常用。
四、wait 更像等待条件工具
asyncio.wait() 返回两组任务:已完成和未完成。
done, pending = await asyncio.wait(
tasks,
return_when=asyncio.FIRST_COMPLETED,
)
它适合更底层的控制,比如:
- 等第一个任务完成;
- 等第一个异常;
- 做自定义取消策略;
- 对 pending 任务继续处理。
但日常“并发拿所有结果”用 gather 更清晰。
五、TaskGroup 为什么更现代
TaskGroup 引入了结构化并发思想。任务不再散落在各处,而是归属于一个明确作用域:
async with asyncio.TaskGroup() as tg:
a = tg.create_task(fetch_a())
b = tg.create_task(fetch_b())
离开 async with 时,任务组会确保内部任务完成或按规则收尾。如果某个任务失败,任务组会处理其他任务取消和异常聚合,让错误传播更有结构。
这比随手 create_task() 然后忘记管理更可靠。
六、怎么选
可以这样判断:
- 简单并发拿全部结果:
gather; - 谁先完成先处理:
as_completed; - 需要 done/pending 和等待条件:
wait; - 一组相关任务要生命周期统一:
TaskGroup; - 后台长期任务:要设计关闭和异常处理,不要只靠
create_task。
七、常见误区与追问
| 工具 | 结果顺序 | 异常/取消关注点 |
|---|---|---|
gather | 默认按传入顺序汇总 | 一个失败时异常传播,其他任务处理要看具体状态 |
as_completed | 按完成先后逐个产出 | 适合边完成边处理 |
wait | 返回 done/pending 集合 | 更像底层等待条件工具 |
TaskGroup | 结构化并发 | 子任务失败会取消同组任务并聚合异常 |
- 误区:gather 会按完成顺序返回结果。
gather的结果顺序对应传入 awaitable 的顺序,不是谁先完成谁先排前面。 - 误区:as_completed 适合需要输入顺序的批处理。 它强调完成即处理,天然打乱顺序;需要顺序结果时要自己携带索引或用
gather。 - 误区:wait 会直接返回任务结果。
wait返回的是 done 和 pending 两组任务,结果还需要对 done 中的 task 调result()或处理异常。 - 追问:TaskGroup 比 gather 新在哪里? 它强调结构化并发,任务作用域清晰,某个子任务失败时同组任务会被协作取消,错误也更集中。
- 追问:什么时候用 wait? 需要
FIRST_COMPLETED、超时后保留 pending、自己决定取消或继续等待时,wait更灵活。 - 追问:批量请求接口怎么选? 要所有结果并保持输入顺序,用
gather;要谁先回来先处理,用as_completed;要强结构化生命周期,用TaskGroup。
记忆钩子:
gather收齐结果,as_completed先到先处理,wait给你任务集合,TaskGroup管住任务生命周期。
八、加强记忆
异步任务组合要按目的选工具:gather 管结果列表,as_completed 管完成顺序,wait 管等待条件,TaskGroup 管一组任务的结构化生命周期。真正成熟的异步代码,不只是把任务跑起来,还要把异常、取消和收尾管起来。