You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为什么CancellationToken取消时SemaphoreSlim未被自动释放?

SemaphoreSlim的WaitAsync传入CancellationToken的作用说明

你观察到的表现确实是.NET的官方设计,CancellationToken作用于等待获取信号量的过程,而非已经获取成功后的持有阶段,核心设计定位和具体作用如下:

核心作用:取消「未完成的信号量等待操作」

WaitAsync(CancellationToken)的取消逻辑仅在你还未成功获取到信号量、仍处于等待队列中时生效:

  • 等待过程中如果令牌触发取消,WaitAsync会立即抛出OperationCanceledException
  • 取消后不会占用信号量的可用计数,CurrentCount不会发生变化,也不会出现自动释放逻辑(因为本来就没拿到信号量)

为什么已经获取信号量后取消令牌不会自动释放?

这是框架的刻意设计,核心是为了保证线程安全:
当你成功调用WaitAsync拿到信号量后,就意味着你已经进入了临界区,正在操作共享资源。如果此时令牌取消就自动释放信号量,会导致其他等待信号量的线程同时进入临界区,直接破坏线程安全,引发不可预期的并发问题。
信号量的释放必须由开发者显式调用Release()控制,你可以在监听取消请求后,先完成临界区的资源回滚、收尾工作,确认安全后再手动释放信号量。

测试代码误区说明

你给出的代码是在await semaphore.WaitAsync(tokenSource.Token)执行完成、已经成功拿到信号量之后才调用Cancel(),此时WaitAsync的逻辑已经执行完毕,传入的令牌已经不再关联任何逻辑,自然不会影响信号量的状态。
如果要验证CancellationToken的作用,可以参考以下测试代码:

var tokenSource = new CancellationTokenSource();
var semaphore = new SemaphoreSlim(0, 1); // 初始可用计数为0,调用WaitAsync会直接进入等待
var waitTask = semaphore.WaitAsync(tokenSource.Token);

tokenSource.CancelAfter(1000); // 1秒后触发取消
try
{
    await waitTask;
}
catch (OperationCanceledException)
{
    Console.WriteLine("等待信号量的过程被取消");
    Console.WriteLine($"当前信号量可用计数:{semaphore.CurrentCount}"); // 输出0,未占用计数
}

最佳实践建议

  • 仅用WaitAsync传入的CancellationToken来避免无限等待信号量,不要期望它处理已获取信号量的释放逻辑
  • 持有信号量期间需要响应取消时,自行在临界区逻辑中监听令牌,完成收尾后显式调用Release()释放

内容的提问来源于stack exchange,提问作者Mike Christensen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 14:24:01