In this tutorial, we will cover the basics of implementing and working with semaphores using the Semaphore and SemaphoreSlim classes from the System.Threading namespace.
Semaphore and SemaphoreSlim Classes
There two main classes of implementing semaphores in C#. The first is the “Semaphore” class.
C# Semaphore Class
This class represents a named semaphore. The named semaphore can be a local or systemwide entity. It works as a thin wrapper around the Win32 semaphore object which are used to control the access to a pool of resources.
C# SemaphoreSlim Class
The second class is the “SemaphoreSlim” class. This provides a lightweight and faster semaphore that is used to handle a single process with very short wait times. Unlike the “Semaphore” class, the “SemaphoreSlim” class does not support named semaphores.
It does, however, have native support for cancellation tokes and wait handle for synchronization.
Implementing a C# Semaphore Using the Semaphore Class
The “Semaphore” class helps to limit the number of threads that can access a resource or a pool of resources at the same time.
The syntax is as follows:
Example Usage:
The following example shows the basics of creating a semaphore using the “System.Threading.Sempahore” class with a maxim count of 3 and an initial count of 0:
using System.Threading;
class Program
{
// 3 count, initial 0
private static Semaphore _pool = new Semaphore(0, 3);
static void Main(string[] args)
{
// 5 threads, 3 at once
for (int i = 1; i <= 5; i++)
{
Thread t = new Thread(new ParameterizedThreadStart(Slot));
t.Start(i);
}
// release 3 threads on enter key.
while (true)
{
Console.ReadLine();
_pool.Release(3);
}
}
static void Slot(object id)
{
Console.WriteLine(id + " is waiting for the slot.");
_pool.WaitOne();
Console.WriteLine(id + " has entered the slot.");
Thread.Sleep(1000 * (int)id);
Console.WriteLine(id + " is leaving the slot.");
_pool.Release();
}
}
In this example, we start by creating a named semaphore called “_pool” with an initial count of 0 and a maximum count of 3. This denotes that the semaphore can only allow up to three threads.
We then start five threads and wait for the semaphore to be released since the initial count is 0. Upon pressing the return key, the semaphore can allow the three threads to access the critical section.
Output:
2 is waiting for the slot.
3 is waiting for the slot.
4 is waiting for the slot.
5 is waiting for the slot.
<>Return Key
1 has entered the slot.
3 has entered the slot.
2 has entered the slot.
1 is leaving the slot.
4 has entered the slot.
2 is leaving the slot.
5 has entered the slot.
3 is leaving the slot.
4 is leaving the slot.
5 is leaving the slot.
Semaphore Implementation with SemaphoreSlim
Let us look at an implementation using the “SemaphoreSlim” class instead of the “Semaphore” class.
using System.Threading;
using System.Threading.Tasks;
class Program
{
private static SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(2);
static void Main()
{
Task[] tasks = new Task[6];
for (int i = 0; i < 6; i++)
{
tasks[i] = Task.Run(() => AccessDatabase(i));
}
Task.WaitAll(tasks);
}
static async Task AccessDatabase(int id)
{
Console.WriteLine($"Task {id} is requesting access.");
await _semaphoreSlim.WaitAsync();
Console.WriteLine($"Task {id} accessing resource");
// resource usage
await Task.Delay(1000);
Console.WriteLine($"Task {id} access terminated..");
_semaphoreSlim.Release();
}
}
In this case, we use the “SemaphoreSlim” to simulate a single process that has multiple threads. We start by initializing the “SemaphoreSlim” with a count of 2. Hence, only two threads can access the resource concurrently.
We then create six tasks and try to access the resource. Since only two are allowed, the remaining tasks have to wait for a space to be available.
Conclusion
In this tutorial, we used the “Semaphore” and “SemaphoreSlim” classes in C# to implement and use the basic semaphores. Feel free to reference the documentation for each of these classes to discover more.