Interview frequency: …
Short answer
Use it as a non-blocking lock that allows a fixed number of threads into a critical section through await semaphore.WaitAsync(), and always release it in finally with semaphore.Release().
Explanation
C#'s lock is fully synchronous and cannot contain await. SemaphoreSlim solves this with the non-blocking WaitAsync(). With a capacity of (1, 1), it acts as an exclusive Async Lock for one request. With a capacity of (10, 10), it limits the critical section to ten concurrent calls to protect external servers from overload.
Code example
private static readonly SemaphoreSlim _gate = new(initialCount: 5, maxCount: 5);
public async Task<Data> FetchThrottledAsync() {
await _gate.WaitAsync();
try {
return await CallExternalServiceAsync();
} finally {
_gate.Release(); // Always required in finally
}
}Common mistakes
- Forgetting _gate.Release() in a finally block, gradually exhausting Semaphore permits and hanging the system indefinitely.
Follow-up questions
- How does System.Threading.Channels work, and what does it offer for the Producer-Consumer pattern?
- How do you process asynchronous work in parallel with Parallel.ForEachAsync in .NET 6+?