Skip to content
预计 1 分钟右侧目录可快速定位

异步任务怎样超时、取消和限流:Cancellation、Timeout 与 Semaphore ​

前置:Task、TaskGroup 与 Structured Concurrency
核心问题:超时、取消和并发限制分别控制什么,为什么只给每个协程套 timeout 仍可能把下游服务压垮?

一个 Tool coroutine 持有临时文件和 HTTP stream;取消时关闭两者后重新抛取消,父 TaskGroup 才能正确收敛。asyncio.timeout(5) 包住 HTTP 调用后,测试不仅断言抛 TimeoutError,还要确认 socket/task 已停止,没有后台泄漏。

asyncio Cancellation ​

在 asyncio 中,Cancellation 是通过 Task.cancel() 等机制向协程注入 CancelledError 的协作停止过程。被取消的 coroutine 需要在 await 边界响应,并在 finally/context manager 中释放资源;吞掉取消会破坏父作用域的结构化生命周期。

asyncio Timeout Scope ​

asyncio timeout 限制的是某个异步等待 scope 最多允许持续多久。到期后真正发生的动作通常是 cancellation,而不是单纯返回一个“超时状态”。

text
await tool()
   ↓ timeout expires
cancel task
   ↓
底层协程清理 socket/file/child task
   ↓
TimeoutError 回到上层 policy

因此 timeout 是否真的有效,还取决于底层代码是否响应 cancellation。它也不能替代 Run deadline:子调用的 timeout 应根据上层剩余 deadline 计算,而不是每层重新获得完整时间预算。

asyncio Semaphore ​

Semaphore 只限制某个资源区域同时有多少协程处于 in-flight,例如 Provider、DB Pool 或高成本 Tool;它不限制单位时间 QPS,也不天然保证公平性。速率限制和多租户配额属于更高层 policy。

它不限制每分钟总请求数,也不保证公平性。

Cancellation 在 Harness 里为什么是传播问题 ​

假设一个 Run 同时启动三个 Tool,其中一个调用外部搜索 API,另一个执行 shell,第三个等待数据库连接。用户点击 Cancel 时,真正需要发生的是:

text
Run CANCEL_REQUESTED
        ↓
Runtime cancel scope
   ├─ search task  → cancel HTTP request
   ├─ shell task   → terminate process / cleanup
   └─ DB task      → stop waiting for pool slot
        ↓
collect cleanup result
        ↓
Run CANCELLED

只把顶层 coroutine 标记为 cancelled 不够。子任务如果吞掉 CancelledError、Tool executor 没有 cleanup,或者 semaphore acquire 没有响应取消,Run 会在 UI 上显示已取消但后台仍持续消耗资源。

timeout 和 cancellation 也不是同一件事:timeout 是程序根据 deadline 主动触发的一种取消来源;用户取消、上游失败、服务 shutdown 也可以触发同一传播链。Semaphore 则负责限制真正进入稀缺资源区的并发量,而不是用来“让任务更快”。

Failure Modes ​

吞掉 CancelledError 让子任务继续运行 ​

症状: child task 捕获广义异常后忽略 cancellation,父 Run 已取消但 Tool/HTTP 仍继续。

为什么危险: 资源和费用泄漏,甚至用户取消后仍产生副作用。

处理: cancellation 作为控制信号向下传播,只在 finally 清理;转成明确 CANCELLED 状态。

如何验证 ​

Case断言
吞掉 CancelledError 让子任务继续运行取消父 Run,所有 child task/HTTP/Tool 在 deadline 内停止且记录 cancelled。

自测 ​

  1. 外层 deadline 已到,但内层 Tool coroutine 吞掉 CancelledError 继续运行,会造成什么资源和状态问题?
  2. Semaphore 限制的是“同时进行多少工作”还是“每秒多少请求”?为什么它不能替代 rate limit?

Sources ​

AI Engineering · Agent · Harness · Backend Systems