as_completed返回异步生成器,须用async for消费并await获取结果;它不支持索引、len等操作,会吞异常且不限并发,需配合semaphore控流,与gather的核心区别在于按完成顺序而非输入顺序返回结果。

as_completed 返回的是迭代器,不是列表
很多人调用 asyncio.as_completed 后直接想用索引取结果,比如 results[0],结果报 TypeError: 'coroutine' object is not subscriptable 或更隐蔽的 TypeError: 'generator' object is not subscriptable——其实它返回的是一个异步生成器(AsyncGenerator),必须用 async for 消费。
- 不能用
list(as_completed(...)),会报错:它不支持同步转列表 - 不能用
len()、index()、切片等操作,因为没实现对应协议 - 典型正确写法是:
async for coro in asyncio.as_completed(tasks): result = await coro - 如果你真需要按完成顺序收集成列表,得自己
append,不能依赖返回值本身可索引
as_completed 会吞掉异常,除非你显式 await
这是最常踩的坑:任务出错了,as_completed 还照常 yield 它对应的协程对象,但你不 await 就永远看不到错误。它只保证“谁先完成谁先出来”,不管完成得是结果还是异常。
- 如果某个
task抛了ValueError,as_completed依然会把它排在队列里;只有当你await coro时,异常才真正抛出 - 常见误写:
for coro in as_completed(tasks): print(coro)—— 这只会打印协程对象地址,啥也不发生 - 正确做法是始终搭配
await,并在外层加try/except捕获单个任务异常 - 如果想区分成功/失败,可以配合
asyncio.create_task+exception()方法检查,但更直觉的方式仍是await后处理
as_completed 不控制并发数,容易压垮下游
as_completed 本身不做限流,它只是调度器视角的“完成顺序观察者”。如果你传入 1000 个未加限制的 HTTP 请求任务,它们会一股脑被 asyncio.create_task 或 asyncio.gather 调度出去——实际并发量取决于事件循环和底层连接池,很可能触发 ConnectionRefusedError 或服务端限流。
- 它不等价于“一次只跑 N 个”,要控并发得用
asyncio.Semaphore包裹每个任务的执行逻辑 - 别指望靠
as_completed(tasks[:10])来限流:这只是切片原始任务列表,没阻塞后续提交 - 典型组合是:
sem = asyncio.Semaphore(5); async with sem: return await aiohttp_session.get(url) - 注意:Semaphore 是 per-task 的,不是 per-as_completed 调用的
与 gather 的核心区别:顺序 vs 完成态
选 as_completed 还是 gather,关键不在“快慢”,而在你是否关心“谁先回来”。gather 严格按输入顺序返回结果(哪怕第 0 个任务最慢);as_completed 打乱顺序,优先吐出已结束的协程。
立即学习“Python免费学习笔记(深入)”;
- 做轮询或“抢答”类逻辑(如查多个 DNS、试连多个备份地址),用
as_completed - 需要保持输入输出一一对应(如批量发消息后按原序存日志),用
gather更安全 -
gather默认return_exceptions=False,一个失败全崩;as_completed则允许部分失败、部分继续 - 性能上无本质差异,都是事件循环调度,但
as_completed多一层生成器包装,开销可忽略









