为什么在取消给定的 cancellationToken 时不释放信号量

Why is semaphore not released when given cancellationToken is cancelled

SemaphoreSlim 有一个采用 CancellationTokenWaitAsync() 方法。我希望信号量在该令牌被取消时被释放,但情况似乎并非如此。考虑以下代码:

var tokenSource = new CancellationTokenSource();
var semaphore = new SemaphoreSlim(1, 1);
// CurrentCount is 1
await semaphore.WaitAsync(tokenSource.Token);
// CurrentCount is 0 as we'd expect

tokenSource.Cancel(); // Token is cancelled

for(var i = 0; i< 10; i++)
{
    var c = semaphore.CurrentCount; // Current count remains 0, even though token was cancelled
    await Task.Delay(1000);
}

即使在令牌被取消后,CurrentCount 仍保持为 0。我猜这是设计使然,但 CancellationToken 究竟用于什么时候不释放信号量令牌被取消?谢谢!

背景

Represents a lightweight alternative to Semaphore that limits the number of threads that can access a resource or pool of resources concurrently.

...

The count is decremented each time a thread enters the semaphore, and incremented each time a thread releases the semaphore. To enter the semaphore, a thread calls one of the Wait or WaitAsync overloads. To release the semaphore, it calls one of the Release overloads. When the count reaches zero, subsequent calls to one of the Wait methods block until other threads release the semaphore. If multiple threads are blocked, there is no guaranteed order, such as FIFO or LIFO, that controls when threads enter the semaphore.

SemaphoreSlim.CurrentCount

Gets the number of remaining threads that can enter the SemaphoreSlim object.

...

The initial value of the CurrentCount property is set by the call to the SemaphoreSlim class constructor. It is decremented by each call to the Wait or WaitAsync method, and incremented by each call to the Release method.

也就是说,每进入一个信号量,就减少剩余可以进入的线程。如果输入成功,则计数递减。

调用正在等待的 CancellationToken 只对等待 WaitAsync 的线程有影响,或者如果您尝试再次等待令牌。

回答这个问题,CancellationToken只是给等待WaitAsync的,不影响有多少线程可以进入SemaphoreSlim

此外

我认为真正中肯的问题是,你是否需要发布一个已经被取消的SemephoreSlim!答案是否定的。等待 SemephoreSlim 尚未成功进入或影响计数,它正在等待,因为没有允许的线程。

最后,您是否需要释放因超时过载而超时的 SemephoreSlim。答案是,这个方法return是一个bool是否进入成功,需要检查那个return值来判断是否可能需要释放。

有趣的事实

这就是为什么您不将等待放在释放 slim 的 try finally 模式中的确切原因。

// this can throw, or timeout
var result = await semaphore.WaitAsync(someTimeOut, tokenSource.Token);

if(!result)
  return;

try
{
   // synchronized work here
}
finally
{
   semaphore.Release();
}

释放不当会导致各种问题,迟早会因为超出最大数量而导致另一个异常。