Asynchronous Synchronization Primitives: `SemaphoreSlim`
1Concept
`SemaphoreSlim` provides asynchronous lock acquisition (`await semaphore.WaitAsync()`) that releases the thread back to the ThreadPool while waiting, making it ideal for async rate limiting and connection throttling.
2Architecture Diagram
3 Concurrent Tasks ──> [ SemaphoreSlim(InitialCount: 2) ] ├── Task 1 ──> Enters (Count = 1) ├── Task 2 ──> Enters (Count = 0) └── Task 3 ──> Awaits non-blockingly until Task 1/2 calls Release()!
3Code Example
C# 13 & .NET 9
using System;
using System.Threading;
using System.Threading.Tasks;
public class SemaphoreSlimDemo
{
private static readonly SemaphoreSlim Throttler = new(initialCount: 2, maxCount: 2);
public static async Task AccessResourceAsync(int id)
{
await Throttler.WaitAsync(); // Non-blocking async wait
try
{
Console.WriteLine($"[Slot Granted] Task {id} accessing database...");
await Task.Delay(20);
}
finally
{
Throttler.Release();
Console.WriteLine($"[Slot Released] Task {id} finished.");
}
}
public static async Task Main()
{
var t1 = AccessResourceAsync(1);
var t2 = AccessResourceAsync(2);
var t3 = AccessResourceAsync(3);
await Task.WhenAll(t1, t2, t3);
}
}4Expected Output
[Slot Granted] Task 1 accessing database... [Slot Granted] Task 2 accessing database... [Slot Released] Task 1 finished. [Slot Granted] Task 3 accessing database... [Slot Released] Task 2 finished. [Slot Released] Task 3 finished.
5Key Takeaways
- ✓Always call `Release()` inside a `finally` block.
- ✓Use `SemaphoreSlim(1, 1)` as an async mutex (`await semaphore.WaitAsync()`).
- ✓Standard `lock` cannot be used with `await`.