I. The Scenario That SemaphoreSlim Can Help You Solve
Below is a clear explanation that would help you understand the situation where you should use SemaphoreSlim (reference [1]).
In the kindergarten 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.
II. Implementation
For detailed information on
SemaphoreSlim, refer to Microsoft’s documentation (link in the References section).
1. Principle of Implementing SemaphoreSlim
// Declare how many available slots
SemaphoreSlim _availableSlot = new(5);
// Hold the slot before doing something
await _availableSlot.WaitAsync();
try
{
// Do something in here
}
finally
{
// Release the slot after doing something
_availableSlot.Release();
}
2. Examples
2.1 Basic Example
I’m using LINQPad to run the code below (before running, you should make a tiny modification :v):
SemaphoreSlim gate = new SemaphoreSlim(1);
async Task Main()
{
for (int i = 0; i < 10; i++)
{
"Start".Dump();
await gate.WaitAsync();
"Do some work".Dump();
gate.Release();
"Finish____".Dump();
}
}
2.2 Advanced Example
Advanced 1:
HttpClient _client = new HttpClient()
{
Timeout = System.TimeSpan.FromSeconds(2)
};
SemaphoreSlim gate = new SemaphoreSlim(1);
void Main()
{
Task.WaitAll(CreateCalls().ToArray());
}
public IEnumerable<Task> CreateCalls()
{
for (int i = 0; i < 5; i++)
{
yield return Item_1();
yield return Item_2();
}
}
SemaphoreSlim _taskThrottle = new SemaphoreSlim(2);
public async Task Item_1()
{
try
{
await _taskThrottle.WaitAsync();
Console.WriteLine(">START ITEM_1");
await Task.FromResult(0);
}
finally
{
Console.WriteLine(">END ITEM_1");
_taskThrottle.Release();
var slot = _taskThrottle.CurrentCount;
Console.WriteLine($"Totals slot after END ITEM_1: ({slot}) OF TOTAL (2)");
}
}
public async Task Item_2()
{
try
{
await _taskThrottle.WaitAsync();
var slot = _taskThrottle.CurrentCount;
Console.WriteLine($"Slot in START_2: ({slot}) OF TOTAL (2)");
">START ITEM_2".Dump();
await Task.FromResult(0);
}
finally
{
Console.WriteLine(">END ITEM_2");
_taskThrottle.Release();
var slot = _taskThrottle.CurrentCount;
Console.WriteLine($"Totals slot after END ITEM_2: ({slot}) OF TOTAL (2)");
}
}