用 asyncio.wait() 等第一个完成的协程需显式指定 return_when=asyncio.FIRST_COMPLETED,传入 Task 列表,结果从 done 集合取;加 timeout 后仍有效,但需手动检查 done 是否为空并取消 pending 任务。

asyncio.wait() 怎么等第一个完成的协程
用 asyncio.wait() 配合 return_when=asyncio.FIRST_COMPLETED,就能拿到最先结束的那个(或那些)协程结果,不用等全部跑完。
常见错误是直接传协程对象列表却忘了 return_when 参数,默认行为是等全部完成(ALL_COMPLETED),跟同步调用没区别。
- 必须显式传
return_when=asyncio.FIRST_COMPLETED,否则不生效 - 传进去的得是
Task或协程对象,但推荐先用asyncio.create_task()包一层,避免协程未被调度 - 返回值是两个集合:
done(已完成的Task)和pending(还在跑的),要从done里取结果
import asyncio
<p>async def fetch(url):
await asyncio.sleep(1)
return f"done: {url}"</p><p>async def main():
tasks = [
asyncio.create_task(fetch("a.com")),
asyncio.create_task(fetch("b.com")),
asyncio.create_task(fetch("c.com")),
]
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
result = done.pop().result() # 取第一个完成的结果
print(result)
加超时后 FIRST_COMPLETED 还管用吗
管用,但超时本身会“生成一个完成项”——也就是 asyncio.wait() 在超时后会把还没完成的协程放进 pending,同时让 done 为空。所以你得判断 done 是否为空,再决定是处理结果还是报错/重试。
容易踩的坑是以为加了 timeout=2 就自动抛异常,其实不会:它只是提前结束等待,返回空的 done 和一堆 pending。
立即学习“Python免费学习笔记(深入)”;
- 超时不是错误,是正常返回路径,必须手动检查
done长度 - 如果只关心“有没有在超时内完成”,就看
len(done) > 0;如果还要取消剩余任务,得遍历pending调用.cancel() -
timeout单位是秒(float),支持小数,比如timeout=0.5
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED, timeout=0.8)
if done:
result = done.pop().result()
else:
print("超时,没一个完成")
for t in pending:
t.cancel()
为什么不能直接用 asyncio.wait_for() 替代
asyncio.wait_for() 是为单个协程加超时设计的,不是为“多个里选最快那个”准备的。它内部会包装成一个 task 并监听完成或超时,一旦超时就直接 raise asyncio.TimeoutError,没法拿到其他协程的状态。
如果你硬要用 wait_for() 套多个协程(比如传一个 gather),那就退化成“等全部完成 or 超时”,完全违背“最先完成”的初衷。
-
wait_for()适合:确定只有一个操作、必须限时完成 -
wait()+FIRST_COMPLETED适合:并发发多个请求,只要一个成功就停,其余可取消 - 混用风险:对
asyncio.gather()用wait_for(),等于又套了一层等待,逻辑变重,且无法单独处理每个子协程的完成时机
取消 pending 协程时要注意什么
取消不是立刻终止,而是设为 CANCELLED 状态,协程得自己响应取消(比如在 await 点检查 task.cancelled() 或捕获 CancelledError)。如果协程里有阻塞 IO 或没做清理,取消可能延迟甚至失效。
最常被忽略的是:取消后不 await 它们,导致 CancelledError 被静默吞掉,或者资源没释放。
- 调用
t.cancel()后,最好也await asyncio.wait(pending)或逐个await t(会立即抛CancelledError)来收尾 - 如果协程里用了
try/finally或异步上下文管理器(async with),取消时会正常走清理流程 - 没加
await的取消,会让 task 残留在事件循环里,下次循环可能报 warning
实际用的时候,重点不在语法多简洁,而在于你是否真需要“抢答式”响应——比如健康检查、降级兜底、竞速请求。这时候 FIRST_COMPLETED 才真正有用;如果只是想防卡死,单个 wait_for 更轻量。


















