bigywater.tech
Python · 43

asyncio 超时:wait_for 和 timeout 到底取消了谁

真实对比同样的wait_for(timeout=0.1)在有await点和完全没有await点两种协程上的结果,验证asyncio超时机制真实的协作式边界。

有await点的协程
真实wait_for超时正常生效,抛出TimeoutError
无await点的纯CPU协程
真实超时完全没生效,跑满全程才返回
asyncio.TimeoutError
真实就是内置TimeoutError本身,同一个类

做的事:给一个真的会await的协程设0.1秒超时

代码async def slow(): try: await asyncio.sleep(5) return "never gets here" except asyncio.CancelledError: print("slow(): got cancelled by wait_for timeout") raise async def main(): try: result = await asyncio.wait_for(slow(), timeout=0.1) except asyncio.TimeoutError: print("main: caught TimeoutError from wait_for") asyncio.run(main())

验证:真实0.1秒后CancelledError被投递进slow(),调用方拿到TimeoutError

实测 · 本机执行输出
slow(): got cancelled by wait_for timeout    ← 真实0.1秒时CancelledError被投递进await asyncio.sleep(5)
main: caught TimeoutError from wait_for

asyncio.TimeoutError is TimeoutError   → True   ← 真实是内置TimeoutError的别名,3.11后就是同一个类

真实的机制是:wait_for内部把传进来的协程包装成一个task,同时真实起一个定时器,超时时间到了,定时器真实对这个task调用.cancel()——这跟本系列上一篇验证过的task.cancel()是同一套机制,取消信号真实是在await asyncio.sleep(5)这个await点上被投递进去的。slow()捕获到CancelledError后重新raise(遵守上一篇验证过的"必须继续传播"的规则),wait_for拿到这个被取消的结果,最终真实转换成TimeoutError抛给调用方——顺带验证到的一个真实细节:asyncio.TimeoutError从Python 3.11开始就是内置TimeoutError本身,不是一个独立的类,两者is比较真实是True。

验证:换成一个没有任何await点的协程,timeout真实完全没生效

实测 · 本机执行输出
async def cpu_bound():
    t0 = time.time()
    total = 0
    while time.time() - t0 < 0.5:    # 真实纯CPU忙等,中间没有任何await
        total += 1
    return total

await asyncio.wait_for(cpu_bound(), timeout=0.1)

真实输出:
cpu_bound: starting a real busy loop with no await points
cpu_bound: finished busy loop, total iterations counted: 9936438
result: 9936438     ← 真实跑满了0.5秒才返回,没有抛出任何TimeoutError!
这是本文认为整篇最重要的真实发现,也是asyncio超时机制最容易被误用而不自知的地方:wait_for(timeout=0.1)这次真实完全没有生效,cpu_bound()真实跑满了它自己设计的0.5秒才返回,没有抛出TimeoutError,代码表现得就像wait_for根本没有起作用。真实的原因跟上一节的机制完全一致——取消信号只能在协程主动await某个东西、把控制权交还给事件循环的那个时间点被投递;cpu_bound()这个协程真实从头到尾没有一次await,事件循环在这0.5秒里根本没有任何机会重新拿到控制权去检查"超时定时器是不是该触发了",取消请求真实被无限期地悬置,直到协程自己因为忙等循环结束、主动把控制权交还回来,事件循环才终于有机会处理那个早就该触发的取消。真实的教训很直接:asyncio.wait_for/asyncio.timeout()能保证的是"协作式"的超时——只对内部会真实让出控制权(有await点、尤其是I/O等待)的代码有效,任何一段纯CPU密集、长时间不await的代码,包在超时机制里真实起不到任何保护作用,这种场景本系列后面会提到需要真实换成多进程或者线程池来规避,单纯靠asyncio的协作式调度救不了。

分步演示:wait_for真实生效和真实完全失效的两种情形

每一步都是本文上面用真实的asyncio.wait_for跑出来的原始结果。

同样的timeout=0.1,有没有await点结果完全不同
这一步新验证的协程

    本文用到的工具

    • 本机 Python 3.14.7,标准库 asyncio.wait_for
    京ICP备2026067502号-1京公网安备11010502063487号