Python asyncio.wait 超时:等待已经返回,为什么任务稍后仍然完成了

前天 4阅读

后台任务被交给 asyncio.wait,并设置等待超时。调用方已经拿到返回值,过一会儿却发现任务仍更新了结果。wait 的超时结束的是这一次等待,不会自动取消尚未完成的任务,也不会因此抛出 TimeoutError。

下面用两只 Event 控制任务确切停在门前,再以 timeout=0 立即检查状态。保存为 demo.py,运行 python demo.py。没有网络请求,也不用靠“睡几毫秒应该足够”来猜测调度进度。

Python asyncio.wait 超时:等待已经返回,为什么任务稍后仍然完成了

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 实际运行并通过断言;结果对应文中固定输入。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。