Concurrency control is a critical part of building responsive and scalable applications. In .NET, SemaphoreSlim is a lightweight synchronization primitive that helps manage access to a limited resource pool. But how do different constructor parameters affect its behavior?
In this post, we’ll explore four common SemaphoreSlim configurations using a visual diagram and a hands-on C# code example to demonstrate how they behave under load.
What Is SemaphoreSlim?
SemaphoreSlim is used to limit the number of threads that can access a resource concurrently. It takes two parameters:
SemaphoreSlim(int initialCount, int maxCount)
- initialCount: How many threads can enter immediately.
- maxCount: The maximum number of threads that can be allowed concurrently.
Visualizing SemaphoreSlim Configurations
Here’s a visual representation of how different configurations behave:

- Blue bars: Initial available slots (how many threads can enter immediately)
- Gray bars: Remaining capacity (how many more threads can be released into the semaphore)
Code Example
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var semaphores = new[]
{
new SemaphoreSlim(1),
new SemaphoreSlim(1, 10),
new SemaphoreSlim(10, 10),
new SemaphoreSlim(10, 20)
};
for (int i = 0; i < semaphores.Length; i++)
{
Console.WriteLine($"\nTesting Semaphore {i + 1}");
var semaphore = semaphores[i];
var tasks = new Task[15];
for (int j = 0; j < tasks.Length; j++)
{
int taskNum = j;
tasks[j] = Task.Run(async () =>
{
if (await semaphore.WaitAsync(TimeSpan.FromMilliseconds(500)))
{
Console.WriteLine($"Task {taskNum} entered Semaphore {i + 1}");
await Task.Delay(200); // Simulate work
semaphore.Release();
}
else
{
Console.WriteLine($"Task {taskNum} timed out on Semaphore {i + 1}");
}
});
}
await Task.WhenAll(tasks);
}
}
}
Expected Output
1. SemaphoreSlim(1)
- Only 1 task enters at a time.
- The rest wait or timeout.
- Output: ~1 task enters every 200ms; others may timeout depending on timing.
2. SemaphoreSlim(1, 10)
- Starts with 1 slot.
- As tasks release, others can enter.
- Up to 10 can be released before hitting
maxCount.
3. SemaphoreSlim(10, 10)
- 10 tasks enter immediately.
- Remaining 5 wait or timeout.
4. SemaphoreSlim(10, 20)
- Same as above, but allows up to 20 concurrent entries if released enough.
SemaphoreSlim is a lightweight synchronization primitive in .NET designed to control access to a limited resource within a single process. It’s optimized for performance and supports asynchronous operations, making it ideal for modern applications that rely on async and await. Unlike traditional locks, SemaphoreSlim allows you to specify both an initial count and a maximum count, enabling fine-grained control over concurrency. However, when you need to coordinate access across multiple processes, SemaphoreSlim falls short—this is where the classic Semaphore comes in.
SemaphoreSlim vs Semaphore: Key Differences
| Feature | SemaphoreSlim | Semaphore |
|---|---|---|
| Purpose | Lightweight semaphore for managing concurrent access within a single process. | Kernel-based semaphore for inter-process synchronization. |
| Implementation | Purely managed code (no OS kernel object). | Wraps an OS kernel semaphore object. |
| Performance | Faster and more efficient for in-process usage (less overhead). | Slower due to kernel transitions and inter-process capabilities. |
| Inter-process support | ❌ No (works only within the same process). | ✅ Yes (can synchronize across processes). |
| Async support | ✅ Yes (WaitAsync for async operations). |
❌ No built-in async support. |
| Constructor parameters | SemaphoreSlim(initialCount, maxCount) |
Semaphore(initialCount, maxCount, name) |
| Resource usage | Lower memory footprint, no kernel handles. | Higher resource usage (kernel handles). |
| Use case | Ideal for limiting concurrency in async code or within a single app domain. | Needed when multiple processes must coordinate access to shared resources. |
Closing Summary: Use SemaphoreSlim for in-process, async-friendly scenarios (e.g., throttling tasks in a web app). Use Semaphore when you need cross-process synchronization (e.g., multiple apps accessing the same file).


You must be logged in to post a comment.