Need to understand the usage of SemaphoreSlim
Here is the code I have but I don't understand what SemaphoreSlim
is doing.
async Task WorkerMainAsync()
{
SemaphoreSlim ss = new SemaphoreSlim(10);
List<Task> trackedTasks = new List<Task>();
while (DoMore())
{
await ss.WaitAsync();
trackedTasks.Add(Task.Run(() =>
{
DoPollingThenWorkAsync();
ss.Release();
}));
}
await Task.WhenAll(trackedTasks);
}
void DoPollingThenWorkAsync()
{
var msg = Poll();
if (msg != null)
{
Thread.Sleep(2000); // process the long running CPU-bound job
}
}
What do await ss.WaitAsync();
and ss.Release();
do?
I guess that if I run 50 threads at a time then write code like SemaphoreSlim ss = new SemaphoreSlim(10);
then it will be forced to run 10 active thread at time.
When one of 10 threads completes then another thread will start. If I am not right then help me to understand with sample situation.
Why is await
needed along with ss.WaitAsync();
? What does ss.WaitAsync();
do?
Solution 1:
In the kindergarden around the corner they use a SemaphoreSlim to control how many kids can play in the PE room.
They painted on the floor, outside of the room, 5 pairs of footprints.
As the kids arrive, they leave their shoes on a free pair of footprints and enter the room.
Once they are done playing they come out, collect their shoes and "release" a slot for another kid.
If a kid arrives and there are no footprints left, they go play elsewhere or just stay around for a while and check every now and then (i.e., no FIFO priorities).
When a teacher is around, she "releases" an extra row of 5 footprints on the other side of the corridor such that 5 more kids can play in the room at the same time.
It also has the same "pitfalls" of SemaphoreSlim...
If a kid finishes playing and leaves the room without collecting the shoes (does not trigger the "release") then the slot remains blocked, even though there is theoretically an empty slot. The kid usually gets told off, though.
Sometimes one or two sneaky kid hide their shoes elsewhere and enter the room, even if all footprints are already taken (i.e., the SemaphoreSlim does not "really" control how many kids are in the room).
This does not usually end well, since the overcrowding of the room tends to end in kids crying and the teacher fully closing the room.
Solution 2:
i guess that if i run 50 thread at a time then code like SemaphoreSlim ss = new SemaphoreSlim(10); will force to run 10 active thread at time
That is correct; the use of the semaphore ensures that there won't be more than 10 workers doing this work at the same time.
Calling WaitAsync
on the semaphore produces a task that will be completed when that thread has been given "access" to that token. await
-ing that task lets the program continue execution when it is "allowed" to do so. Having an asynchronous version, rather than calling Wait
, is important both to ensure that the method stays asynchronous, rather than being synchronous, as well as deals with the fact that an async
method can be executing code across several threads, due to the callbacks, and so the natural thread affinity with semaphores can be a problem.
A side note: DoPollingThenWorkAsync
shouldn't have the Async
postfix because it's not actually asynchronous, it's synchronous. Just call it DoPollingThenWork
. It will reduce confusion for the readers.
Solution 3:
Although I accept this question really relates to a countdown lock scenario, I thought it worth sharing this link I discovered for those wishing to use a SemaphoreSlim as a simple asynchronous lock. It allows you to use the using statement which could make coding neater and safer.
http://www.tomdupont.net/2016/03/how-to-release-semaphore-with-using.html
I did swap _isDisposed=true
and _semaphore.Release()
around in its Dispose though in case it somehow got called multiple times.
Also it is important to note SemaphoreSlim is not a reentrant lock, meaning if the same thread calls WaitAsync multiple times the count the semaphore has is decremented every time. In short SemaphoreSlim is not Thread aware.
Regarding the questions code-quality it is better to put the Release within the finally of a try-finally to ensure it always gets released.