c# 如何用 CancellationTokenSource 控制一组任务的超时

可以,CancellationTokenSource 通过共享同一 CancellationToken 实例实现多任务协同超时控制,需各任务主动监听或传入 token,否则取消无效。

CancellationTokenSource 能否直接控制多个任务超时?

可以,但不是靠“绑定”或“注入”,而是通过共享同一个 CancellationToken 实例来实现协同取消。关键在于所有任务都主动监听该 token 的 IsCancellationRequested 状态,或在支持取消的 API(如 Task.DelayHttpClient.GetAsync)中传入它。

如何用单个 CancellationTokenSource 同时触发多个正在运行的任务?

必须确保每个任务内部正确响应取消信号——否则超时不会生效。常见错误是只调用 Cancel() 却没在任务里检查 token 或没把 token 传给底层异步方法。

  • CancellationTokenSource 时指定超时毫秒数:var cts = new CancellationTokenSource(3000);
  • cts.Token 传给每个支持取消的异步操作(如 Task.Run(..., cts.Token)httpClient.GetAsync(url, cts.Token)
  • 若任务含自定义循环或长时间同步操作,需手动轮询 token.IsCancellationRequested 并提前退出
  • 不要忽略 OperationCanceledException:它不是异常,而是取消的正常信号,应捕获后不重抛(除非要向上传递)
var cts = new CancellationTokenSource(2000);
var tasks = new[]
{
    Task.Run(() => LongRunningWork(cts.Token), cts.Token),
    HttpClient.Default.GetAsync("https://httpbin.org/delay/1", cts.Token),
    Task.Delay(1500, cts.Token)
};

try
{
    await Task.WhenAll(tasks);
}
catch (OperationCanceledException) when (cts.IsCancellationRequested)
{
    // 所有任务因超时被取消,此处处理超时逻辑
}
finally
{
    cts.Dispose();
}

为什么有些任务没被 Cancel() 中断?

根本原因是任务未参与协作式取消。例如:

  • 纯 CPU 密集型循环没检查 token.IsCancellationRequested
  • 调用了不接受 CancellationToken 的第三方方法(如老版本 WebClient.DownloadString
  • Task.Run(() => { Thread.Sleep(5000); }) 这种方式启动,线程休眠无法被中断
  • 在 catch 块中吞掉了 OperationCanceledException 却没重新 throw 或调用 token.ThrowIfCancellationRequested()

超时控制中容易被忽略的细节

超时不是“硬杀”,而是协作通知;真正决定是否停止的是任务自身。最常漏掉的是资源清理和状态一致性:

  • CancellationTokenSource.Cancel() 会立即设置 IsCancellationRequested == true,但不会终止线程或释放句柄
  • 如果任务已进入不可中断的 I/O(如 socket 阻塞读),需依赖底层协议超时(如 HttpClient.Timeout)配合使用
  • 多个任务共用一个 CancellationTokenSource 时,任一任务主动调用 cts.Cancel() 都会波及全部——这不是 bug,是设计使然
  • 不要在任务内部调用 cts.Cancel(),除非你明确想主动触发全局取消(比如某个子任务失败后提前结束整个组)