Reputation: 2347
Consider the following code, executed with .NET 5
:
using System;
using System.Threading;
public class Program
{
public static void Main(string[] args)
{
var semaphore = new SemaphoreSlim(0);
var cts = new CancellationTokenSource();
var entrance = semaphore.WaitAsync(cts.Token);
cts.Cancel();
cts.Dispose();
semaphore.Release();
Console.WriteLine("Entrance status: " + entrance.Status);
Console.WriteLine("Current count: " + semaphore.CurrentCount);
}
}
When I run this code, the application complete successfully and I got the following result:
Entrance status: WaitingForActivation
Current count: 0
But since I cancel the WaitAsync
operation before releasing the semaphore, I was expecting the semaphore CurrentCount
to be 1
and the Task
to be in the Canceled
status.
Before posting the question, I ran the code within https://dotnetfiddle.net and surprisingly, it ran as I expected with the .NET Framework 4.7.2
.
Do I find a bug in .NET 5 SemaphoreSlim
?
Is there a way to get the former behavior in .NET 5
?
PS: I find a way to get the former behavior by setting a timeout to the WaitAsync
operation, but that's not an acceptable answer to my opinion.
EDIT according to Guru Stron comments
Awaiting entrance
before the Release
statement produces an OperationCancelledException
as I would expect.
But awaiting after the statement does not throw any exception and the semaphore is "consumed".
Where both cases produce an error in former .NET Framework
.
Upvotes: 0
Views: 390
Reputation: 457067
CancellationTokenSource.Cancel
is a request to cancel. The cancellation tokens become canceled immediately, but Cancel
does not guarantee to wait, blocking the current thread, until all operations listening to that cancellation token have been canceled (and their parent operations, etc).
In other words, your code has an inherent race condition: whether the Release
or the CancellationToken
's cancellation will reach the WaitAsync
operation first. If the Release
reaches it first, then the semaphore will be acquired. If the cancellation reaches it first, then the wait will be canceled.
await
ing the WaitAsync
operation's task before calling Release
resolves this race condition by (a)waiting the operation, forcing it to see the cancellation before the Release
is sent.
Do I find a bug in .NET 5 SemaphoreSlim?
No. The code was depending on a race condition, both on .NET Framework and on .NET 5. The result of the race condition is not guaranteed to have any specific result on either platform.
Is there a way to get the former behavior in .NET 5?
No. I recommend reworking the code so that it does not depend on a race condition. Then it will work correctly for both platforms.
It's possible that the code is using SemaphoreSlim
for something it wasn't designed for. E.g., if you need a queue of asynchronous work, then build a queue of asynchronous work instead of trying to use a SemaphoreSlim
as one.
Upvotes: 1