asyncio.timeout 不能直接 await,因为它是返回 Timeout 实例的上下文管理器,必须配合 async with 使用;它仅在进入时启动计时、退出时检查超时并抛出 TimeoutError,不自动取消协程,需手动创建任务并 cancel 后 await 以确保取消信号被消费。

asyncio.timeout 为什么不能直接 await?
因为 asyncio.timeout 本身不是协程,而是一个上下文管理器(context manager),返回一个 Timeout 实例。直接 await asyncio.timeout(5) 会报 TypeError: object Timeout can't be used in 'await' expression —— 这是最常见的误用起点。
它必须配合 async with 使用,且只在进入和退出时生效:进入时启动计时,退出时检查是否超时并抛出 asyncio.TimeoutError。
- 错误写法:
await asyncio.timeout(3) - 正确写法:
async with asyncio.timeout(3): await some_coro() - 超时后异常由
async with块内部抛出,不会自动“取消”正在运行的协程 —— 这点极易被忽略
如何确保超时后真正取消目标任务?
asyncio.timeout 只负责抛异常,不负责取消。如果 some_coro() 内部没做取消响应(比如阻塞了 I/O、没检查 asyncio.current_task().cancelled()),它会继续执行,只是外层已收到超时异常。
要实现“真取消”,得把目标协程包装进 asyncio.create_task(),再用 async with + wait_for 或手动 cancel():
立即学习“Python免费学习笔记(深入)”;
try:
async with asyncio.timeout(2):
task = asyncio.create_task(fetch_data())
await task
except asyncio.TimeoutError:
task.cancel()
try:
await task # 让取消传播完成
except asyncio.CancelledError:
pass
- 单独用
asyncio.wait_for(coro, timeout=2)更简洁,它内部自动处理取消逻辑 - 但
wait_for在 Python 3.11+ 已被标记为 deprecated,官方推荐回归async with asyncio.timeout()+ 显式任务管理 - 关键点:取消后务必
await task一次,否则取消信号可能未被消费,协程残留运行
嵌套 timeout 和 cancel_scope 的行为差异
多个 async with asyncio.timeout() 嵌套时,最内层超时会先触发,但外层 timeout 计时器仍在运行 —— 它们彼此独立,不构成“叠加”或“继承”关系。
例如:
async with asyncio.timeout(5): # 外层 5 秒
await asyncio.sleep(1)
async with asyncio.timeout(1): # 内层 1 秒
await asyncio.sleep(2) # 这里会立即超时
此时抛出的是内层的 TimeoutError,外层 timeout 计时器仍剩余约 4 秒,但因已退出 async with 块,不再起作用。
- 不要指望嵌套 timeout 能“延长”或“重置”超时时间
- 若需更精细的取消范围控制(比如只取消某段逻辑,不影响外层),应考虑
anyio.CancelScope或手动Task.cancel() - Python 标准库中没有类似
anyio那样的 cancel scope 层级传播机制,纯靠开发者显式管理
timeout 参数支持 float、int,但不支持 datetime 或 timedelta
asyncio.timeout() 只接受数值型参数(秒),单位是浮点秒。传入 datetime.now() + timedelta(seconds=3) 或 timedelta(seconds=3) 会直接报 TypeError。
如果你有绝对截止时间需求(比如“必须在 15:00:00 前完成”),得自己算剩余秒数:
deadline = time.time() + 3
async with asyncio.timeout(deadline - time.time()):
await long_running_op()
- 注意
time.time()是 wall-clock 时间,受系统时钟跳变影响;高精度场景建议用time.monotonic() - 每次进入
async with都会重新计算剩余时间,所以即使前面有 await,只要还没超时,剩余时间就动态更新 - 传入负数或 0 不会立即超时,而是立刻抛
TimeoutError—— 这可以用于“快速失败”逻辑,但需谨慎
实际用起来,最易漏掉的是“取消后 await task”那一步,以及误以为 timeout 会自动中断协程。它只管抛异常,其余都得你亲手收尾。


















