What are Semaphores?
2021-05-29
You may be asking yourself this question. In this post we will take a look at what a Semaphore is and why you might need to use it.
So the definition of a Semaphore
is as follows:
A semaphore is a variable or abstract data type used to control access to a common resource by multiple processes and avoid critical section problems in a concurrent system such as a multitasking operating system.
What does this mean in terms of C#?
So in C# we have the concept of a thread . Threads can run code concurrently in our application. It is especially useful for running CPU bound work. These threads are in scope of the above quote the "multiple processes".
So how does a Semaphore fit in with threads?
I will explain this with an example. Say we have a method, that takes in a string with some text content, then it will write this text content to a path on the filesystem.
async Task WriteTextToFile(string text)
{
await File.WriteAllTextAsync(text, "C:\Code\MyFile.txt");
}
So this will work fine as it is. However, now imagine a situation where two threads are running this method at the same time.
They are both going to try and write to the same file, at nearly the same time. This is going to cause some trouble, which thread wins? and what gets written to the file?
What we can do to fix this is use a SemaphoreSlim to ensure that only 1 thread is acessing this code at a time, blocking the execution temporarily.
In our code that calls our WriteTextToFile
we can create a SemaphoreSlim
and pass it through. (We create this with 1 as the paramater to tell the semaphore, that it can only be waited on 1 at a time)
async Task Main()
{
var semaphore = new SemaphoreSlim(1);
var textContent = new[] { "Test1", "Test2" };
await Task.WhenAll(textContent.Select(t => WriteTextToFile(t, semaphore)));
}
async Task WriteTextToFile(string text, SemaphoreSlim semaphore)
{
await semaphore.WaitAsync();
await File.WriteAllTextAsync("C:\\Code\\MyFile.txt", text);
semaphore.Release();
}
In our method we can now await the semaphore, this blocks the execution of anything using the semaphore until we release it. We then write the contents to the file, and release the semaphore; which allows the other thread to aquire the lock of the semaphore and write its contents to the file.
This now means that the code first writes "Test1" to the file and then afterwards writes "Test 2" to the file. No more conflicts.
See the docs on when is best to use a Semaphore or a SemaphoreSlim
here.
Conclusion
Semaphores are great for taking control of IO bound operations and places where you would potentially run into race conditions. If you need a similar functionality but don't need to use async code in the semaphore, consider using a lock
statement.