Python asyncio.wait 超时:等待已经返回,为什么任务稍后仍然完成了
后台任务被交给 asyncio.wait,并设置等待超时。调用方已经拿到返回值,过一会儿却发现任务仍更新了结果。wait 的超时结束的是这一次等待,不会自动取消尚未完成的任务,也不会因此抛出 TimeoutError。
下面用两只 Event 控制任务确切停在门前,再以 timeout=0 立即检查状态。保存为 demo.py,运行 python demo.py。没有网络请求,也不用靠“睡几毫秒应该足够”来猜测调度进度。
AI模型生成概念示意:观察者停止等待时,任务仍留在门后,开门后可以继续完成;不是异步调试器截图。
import asyncio
async def main():
started = asyncio.Event()
gate = asyncio.Event()
changes = []
async def worker():
started.set()
await gate.wait()
changes.append("finished")
return 7
task = asyncio.create_task(worker())
await started.wait()
done, pending = await asyncio.wait({task}, timeout=0)
print("done/pending:", len(done), len(pending))
print("cancelled:", task.cancelled())
print("changes before release:", changes)
assert done == set() and pending == {task}
assert not task.done() and not task.cancelled()
assert changes == []
gate.set()
result = await task
print("result:", result)
print("changes after release:", changes)
print("task done:", task.done())
assert result == 7 and changes == ["finished"]
assert task.done() and not task.cancelled()
asyncio.run(main())超时把未完成对象交回调用方
done/pending: 0 1 表示没有任务完成,有一项仍未完成。cancelled: False 与 changes before release: [] 一起表明:任务既没有被取消,也没有越过关闭的 gate。started 先确认协程已经运行到等待点,避免把尚未开始与正在等待混为一谈。
timeout=0 在这里是一次立即返回的状态检查,不是性能测量。无论机器快慢,gate 都未打开,所以示例可以稳定保留 pending。它说明超时参数不承担取消动作,不能据此推断真实业务任务通常需要多长时间。
任务的后续去向仍需要有人负责
打开 gate 并再次 await 同一任务后,依次输出 result: 7、changes after release: ['finished'] 和 task done: True。第二次等待没有重新创建任务,只是在收取原任务的最终结果。第一次 wait 返回后,任务对象和协程状态一直保留着。
如果业务允许任务稍后完成,就要保存它的引用,安排收取结果或异常,并明确完成结果会写到哪里。不能把 pending 集合随手丢弃,然后让后台工作在无人管理的情况下继续。done 与 pending 都是集合,没有与输入列表相同的顺序;多任务场景应保留任务到业务编号的映射。
若超时后业务要求停止,应对仍在运行的任务明确请求取消,再等待清理结束。cancel 只是请求,协程需要在可取消的等待点响应;它也不会撤销任务此前已经完成的外部动作。不要把“已请求取消”写成“所有效果已回滚”。本例没有外部动作,也没有隐藏的遗留任务。
wait 与 wait_for 的超时行为不同,替换接口时应重新核对任务所有权。Python 3.11 起,wait 不再接受直接传入的协程对象;示例先 create_task,再传任务集合。空集合也不是合法输入,调用前应处理没有任务的业务分支。
资料核对日期:2026年10月2日。代码在本地 Python 3.12.14 实际运行并通过断言;结果对应文中固定输入。


