asyncio.wait 不能表达任务依赖,仅并发等待完成;正确方式是用 await 显式串行或 create_task 配合 await;复杂依赖宜用 Queue 或 Event 协调。

asyncio.wait 不能直接表达“任务A完成后才启动任务B”
很多人误以为 asyncio.wait 能像同步代码那样控制执行顺序,其实它只是并发等待一组 Task 或 Future 完成,并不提供依赖调度能力。它返回已完成和未完成的任务列表,但不会阻塞、也不会自动触发下游任务。
常见错误现象:asyncio.wait([task_a, task_b]) 后直接 await task_b —— 这会报 InvalidStateError,因为 task_b 可能根本没被 schedule(还没 start)。
- 真正需要“依赖”,得靠
await显式串行:先await task_a,再await task_b - 若想保持并发性又带依赖,应把依赖逻辑写进协程内部,比如
async def b_after_a(): await a(); return await b() -
asyncio.wait的典型用途是“等任意一个完成”(return_when=asyncio.FIRST_COMPLETED)或“等全部完成”,不是流程编排工具
用 asyncio.create_task + await 实现轻量级依赖链
最直白、最不容易出错的方式,就是放弃用 wait 做调度,改用 await 表达先后关系。Python 3.7+ 的 asyncio.create_task 能立刻调度协程,配合 await 就天然形成依赖。
使用场景:A 必须成功返回结果后,B 才能用该结果发起请求;或 A 是初始化动作(如连接数据库),B 是后续操作。
立即学习“Python免费学习笔记(深入)”;
async def fetch_user():
return {"id": 123}
<p>async def get_profile(user_id):
return {"name": "Alice"}</p><p>async def main():
user = await fetch_user() # A 完成后才往下走
profile = await get_profile(user["id"]) # B 依赖 A 的输出
return profile</p>- 不要提前
create_task(fetch_user())再等它——除非你明确需要“启动但不等待”,否则纯属增加复杂度 - 如果 A 和 B 本身可并行(比如 B 不依赖 A 结果),那才考虑
asyncio.gather(A(), B()) -
create_task启动后若不 await,可能在 main 结束前就被 cancel,尤其在短生命周期脚本中容易漏掉
asyncio.wait 的正确用法:按完成状态做分支处理
asyncio.wait 的价值在于响应式判断,比如“只要有一个失败就中止”,或“拿到第一个响应就取消其余”。它不适合构建 DAG,但适合做容错/超时/竞速逻辑。
参数差异:return_when 是关键开关,常用值有 asyncio.FIRST_COMPLETED、asyncio.ALL_COMPLETED、asyncio.FIRST_EXCEPTION。
done, pending = await asyncio.wait(
[fetch_from_cache(), fetch_from_api()],
return_when=asyncio.FIRST_COMPLETED
)
for t in done:
if not t.exception():
result = t.result()
break
for t in pending:
t.cancel()
- 注意:
done里是Task对象,不是返回值,必须调t.result()获取结果 - 未被 await 的 pending 任务不会自动清理,必须显式
cancel(),否则可能泄露或继续运行 - 若任一任务抛异常,默认不会传播,需手动检查
t.exception(),否则可能静默失败
依赖复杂时,别硬扛——用 asyncio.Queue 或状态变量协调
当依赖不止两层(比如 A→B→C,且 C 还要等 D),或者有动态分支(B 是否运行取决于 A 返回值),硬套 await 链会嵌套过深、难以维护。这时更适合用通信机制解耦。
性能影响:相比纯 await,asyncio.Queue 多一次协程切换和内存拷贝,但换来清晰的职责分离;对大多数 I/O 密集型任务,这点开销可忽略。
queue = asyncio.Queue() <p>async def producer(): res = await fetch_data() await queue.put(res)</p><p>async def consumer(): data = await queue.get() await process(data)</p><h1>启动两个 task 并发跑,consumer 自动等 producer put</h1>
- Queue 是线程安全的,但只在同一线程内协程间有效;跨线程要用
loop.call_soon_threadsafe - 如果 producer 可能失败,consumer 会永远 await
queue.get(),务必加 timeout 或用queue.empty()配合轮询(不推荐) - 简单依赖用
await,多对多或条件依赖用Queue或asyncio.Event,这是分界点
事情说清了就结束。asyncio 的依赖本质是协程调度顺序,不是任务对象之间的“绑定”;所有看似高级的 wait/gather/ensure_future,都绕不开一个事实:await 才是唯一可靠的同步点。


















