asyncio.as_completed不能直接流式获取结果,因为它返回的是task对象而非结果值,必须显式await每个task才能得到实际结果;若不await则停留在task状态,且异常未捕获会导致整个流中断。

asyncio.as_completed 为什么不能直接“流式”获取结果?
asyncio.as_completed 本身确实按完成顺序返回 Task 对象,但它返回的是协程对象(await task 才能得到结果),不是结果本身。很多人误以为它像生成器一样“yield 结果”,实际要手动 await 每个 task 才能拿到值——不 await,就卡在 Task 状态,没法真正“流式处理”。
常见错误现象:for f in asyncio.as_completed(tasks): print(f) 输出一堆 <task pending ...></task>,而不是你想要的计算结果。
- 必须用
await f或f.result()(后者需确保已 done 且无异常) - 如果任务抛异常,
f.result()会直接 raise;而await f同样会传播异常,需 try/catch - 不要在同步循环里混用
await:整个外层函数必须是async def
正确写法:用 async for + await 获取每个完成结果
最简可行流式处理模式就是在一个 async def 函数里,遍历 asyncio.as_completed 并逐个 await:
import asyncio
<p>async def fetch(url):
await asyncio.sleep(1) # 模拟网络延迟
return f"done: {url}"</p><p>async def main():
tasks = [fetch(u) for u in ["a", "b", "c"]]
for f in asyncio.as_completed(tasks):
result = await f # ← 关键:这里 await 才真正取值
print(result)</p><p>asyncio.run(main())</p>
注意:这个 for 循环是同步语法,但内部 await 让它实际是异步调度的——as_completed 保证了谁先 finish 谁先被 await 到,所以输出顺序不依赖列表顺序。
- 性能影响:没有额外线程或进程开销,纯事件循环调度
- 兼容性:Python 3.7+ 都支持;3.6 需用
loop.run_until_complete - 如果想边收结果边做后续处理(比如写文件、发 HTTP),就在这里
print(result)的位置插入逻辑
如何安全处理异常并继续流式消费?
只要有一个 Task 抛异常,await f 就会中断当前迭代。想“跳过失败任务、继续收其他结果”,得显式捕获:
for f in asyncio.as_completed(tasks):
try:
result = await f
print("success:", result)
except Exception as e:
print("failed:", type(e).__name__)
常见错误场景:TimeoutError、ConnectionError、JSON 解析失败等。不加 try,整个流就停了。
-
f.exception()可以提前检查是否失败(不用 await),但依然要调用一次await f或f.done()才能确认状态 - 别用
f.cancel()在这里——as_completed返回的 task 已经在跑,cancel 可能引发CancelledError,反而干扰流式逻辑 - 如果失败任务需要重试,建议在
fetch()内部封装重试逻辑,而不是在外层循环里重新创建 task
和 asyncio.gather 相比,什么情况下必须用 as_completed?
当你需要「结果一出来就立刻处理」,而不是等全部完成再统一收,就必须用 as_completed。比如:实时日志聚合、爬虫遇到第一个 200 就停止、批量调用中优先响应最快的下游服务。
asyncio.gather 是全量阻塞式收集:所有 task 必须都完成(或失败)才返回 list,中间无法干预。
- 流式吞吐量敏感场景:用
as_completed可以让 CPU/IO 更早进入下一轮处理,降低端到端延迟 - 内存敏感场景:
gather会把所有结果暂存在内存 list 里;as_completed可以边处理边释放(比如写磁盘后del result) - 注意:两者底层都用同一个 event loop,调度开销几乎一致;差异只在返回时机和控制流结构
真正容易被忽略的是:as_completed 不改变 task 的执行逻辑,它只是换个方式“看结果”。如果你忘了 await,或者没处理异常,流就断在第一处问题上——它不会自动跳过,也不会告诉你哪里卡住了。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











