Home > Blockchain >  Why is semaphore not released when given cancellationToken is cancelled
Why is semaphore not released when given cancellationToken is cancelled

Time:11-18

SemaphoreSlim has a WaitAsync() method that takes a CancellationToken. I'd expect the semaphore to get released when that token is cancelled, but this doesn't seem to be the case. Consider the code below:

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);
}

Even after the token gets cancelled, the CurrentCount remains at 0. I'm guessing this is by design, but what exactly is the CancellationToken used for if not to release the semaphore when the token is cancelled? Thanks!

CodePudding user response:

Background

SemaphoreSlim Class

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.

That's to say, every time you enter a semaphore you reduce the remaining threads that can enter. If you have entered successfully, the count is decremented.

Calling the CancellationToken that is being awaited only has an effect on threads awaiting the WaitAsync, or if you try to await the token again.

To answer the question, the CancellationToken is solely for the awaiting WaitAsync, it has no affect on how many threads can enter the SemaphoreSlim.

Further more

I think the real and pertinent question is, do you need to release a SemephoreSlim that has been cancelled! and the answer is no. An awaiting SemephoreSlim has not successfully entered or affected the count anyway, its awaiting because there are no threads permissible.

And lastly, do you need release a SemephoreSlim that has been timed out via the timeout overload. The answer to that is, this method returns a bool as to whether it was entered successfully, that return value needs to be checked to determined whether it may need to be released

Fun fact

This is the exact reason why you don't put the wait inside a try finally pattern that releases the slim.

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

if(!result)
  return;

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

Releasing inappropriately will cause all sorts of problems and result in another exception sooner or later because you are exceeding the maximum count

  • Related