Asyncio.gather 和 asyncio.as completed 分别是什么?
面试时被问到 asyncio.gather 和 asyncio.as_completed,我通常会先说一个形象的比喻,帮对方快速抓住本质,再落到代码上。
🧠 一句话抓住本质¶
-
gather:像老师收卷子——必须等所有人交齐,我才拿到全部结果,顺序和你交卷的顺序(或安排好的顺序)一致。 -
as_completed:像客服中心处理工单——谁先处理完,我就立刻响应谁,不需要等最慢的那个人。
📋 先看签名的差别(很重要)¶
# gather:一批协程,返回一个列表,保持传入顺序
results = await asyncio.gather(*tasks)
# as_completed:返回一个异步迭代器,按完成顺序逐个产出
for coro in asyncio.as_completed(tasks):
result = await coro
-
gather一次性拿到所有结果,如果某个任务抛异常,默认会立刻抛出,但也支持return_exceptions=True收集异常。 -
as_completed是按完成顺序逐个处理,异常不会中断其他任务,你可以在循环里单独捕获。
⚙️ 用图示理解执行过程¶
假设有三个任务,耗时分别为 快(1s)、中(2s)、慢(3s):

as_completed 的时间线:
[快]--1s--> ✓ 先拿到结果,立即处理
[中]------2s------> ✓ 第二个拿到
[慢]---------3s---------> ✓ 最后拿到
👉 如果“快”的结果足以驱动后续逻辑(比如提前返回),用 as_completed 可以大幅降低整体响应时间。
🧩 代码对比 + 常见场景¶
场景 1:批量调用多个 API,需要所有结果,且顺序重要¶
async def fetch(url):
# 模拟请求
...
urls = ["/api/a", "/api/b", "/api/c"]
tasks = [fetch(u) for u in urls]
results = await asyncio.gather(*tasks)
# results[0] 一定对应 /api/a,即使 /api/b 先回来
场景 2:调用多个搜索源,只要有第一个成功就返回¶
async def search_provider(provider):
...
providers = [search_provider(p) for p in ["A", "B", "C"]]
for coro in asyncio.as_completed(providers):
try:
result = await coro
# 拿到第一个成功结果,取消其余任务
for t in providers:
t.cancel()
return result
except Exception:
continue
这里如果用 gather,你得等所有任务都结束才能判断,哪怕 A 已经秒回,你也要等 B、C 超时或失败,不合适。
🔀 异常处理的差异(面试常挖的坑)¶
记住一个原则:
-
想要“全有或全无”式的批量结果,用
gather(并可能搭配return_exceptions)。 -
想要“看谁先回来先处理”,并对失败有独立容忍度,用
as_completed。
🛠️ 我实际项目中常用的组合拳¶
有时不是非黑即白,我会把两者结合:
先用 as_completed 拿到第一个成功结果作为主数据,再用 gather 收集剩余结果做缓存预热或日志记录。重点是根据业务对响应及时性和结果完整性的取舍来选择。
🔚 最后换个角度理解¶
很多同学纠结“为什么有了 gather 还要 as_completed”,其实你只要想:这两个工具解决的是不同维度的并发编排需求。
gather 重“集合”,as_completed 重“流式”。
就像你管理一个团队,有时候需要等全员报告再决策(gather),有时候要谁先完成就先采纳谁的意见(as_completed)。
把工作流想清楚,选哪个就一目了然了。