In this article, we are going to learn how to use the Mutex class in C#.
Multithreading can significantly boost an application’s performance, but it also brings challenges related to synchronizing and coordinating tasks across different threads (and even processes). With C#, we have a variety of tools at our disposal to overcome these challenges and build a resilient, performant application that makes full use of the available computing resources. One such tool is the Mutex class.
Let’s start.
What Are Synchronization Primitives?
In many cases, trying to access a resource simultaneously from multiple threads will cause corruption. For example, we can only write to a file from one place at a time—otherwise, the operations will mangle the data up and the file will get corrupt! Synchronization primitives are tools we have at our disposal to safely access these types of resources from a multi-threaded environment. They do this by making sure we don’t make more simultaneous requests to a resource than is acceptable.
Overview of a Mutex in C#
A Mutex is one of the synchronization primitives that the operating system provides which we can use in C#. Simply put, a Mutex
restricts access to a resource so that only one thread can access it at a time.
It does this through the concept of ownership: threads must only access the resource protected by the Mutex
if they own the Mutex
. Any thread may attempt to acquire ownership of the Mutex
upon which one of these outcomes may happen:
- If the
Mutex
is free, the requesting thread will successfully acquire its ownership - If another thread currently owns the
Mutex
, the requesting thread will be blocked until the currently owning thread releases it - If the thread that currently owns the
Mutex
(or last owned it) exits without releasing it, anAbandonedMutexException
exception will be thrown.
This means that it is not possible for more than one thread to own the Mutex
at once.
Basic Usage of a Mutex in C#
Let’s create a Mutex
using its constructor:
var mutex = new Mutex();
Doing this, we’ll get a Mutex
that’s local and that no thread initially owns. If we instead want the current thread to own the Mutex
, we can specify the first parameter, initiallyOwned
as true
. We’ll soon learn what we mean by a local Mutex
and also create a Mutex
that isn’t local.
We should have a reference to the Mutex
object from each thread we might need to access a protected resource in. Once that need arises, from that same thread we want to access the resource, we’ll invoke its WaitOne
method:
mutex.WaitOne();
This method will only return once we’ve acquired ownership of the Mutex
. After this point, we can safely access the protected resource without the risk of a race condition.
Once we’re done with the resource, we must take care to release the Mutex
so that other threads can acquire it:
mutex.ReleaseMutex();
If our thread terminates without releasing the Mutex
, it will be put in an abandoned state. The next thread that acquires the Mutex
will encounter an AbandonedMutexException
exception on the WaitOne
method. It’s good practice to handle this exception when waiting on a Mutex
.
Named System Mutexes in C#
Local Mutex
objects are sufficient for coordinating access to a protected resource in a single process. However, if we have multiple processes that need to access a single resource, we need a Mutex
that can span all those processes. We call this type of Mutex
a named system Mutex
—it has a name associated with it and any process on the system can access the same Mutex
with its name.
To create a named system Mutex
, we provide the name
parameter to the constructor (this is the second parameter):
var mutex = new Mutex(initiallyOwned: false, "MyMutex");
All Mutex
objects on the system with this name refer to the same underlying Mutex
in the operating system. The rules we discussed still apply: only one thread can own this Mutex
at a time (even across different Mutex
instances as long as they all have the same name).
We can prefix Mutex
names with either Global\
or Local\
. The exact behavior of these prefixes varies by platform, but Mutex
instances with the former prefix are generally visible throughout the entire system; those prefixed with the latter may be more isolated.
On certain platforms which isolate processes well, we may have to prefix all Mutex
names with Global\
to get the behavior we expect.
Example Mutex Use-Case: File IO
Let’s start with a very simple method that writes some data to a file:
public static void WriteNumbers(string fileName) { for (int number = 1; number <= 50; number++) { File.AppendAllText(fileName, $"{number} "); Thread.Sleep(100); } }
It appends all integers from 1 to 50 to the file each time we execute it.
However, we haven’t implemented any synchronization between threads or processes yet. If we execute this code multiple times simultaneously, we will see multiple different number streams interweaved into the file.
Let’s fix this:
public static void WriteNumbers(string fileName) { using var mutex = new Mutex(initiallyOwned: false, "Global\\numbers_output"); mutex.WaitOne(); for (var number = 1; number <= 50; number++) { File.AppendAllText(fileName, $"{number} "); Thread.Sleep(100); } mutex.ReleaseMutex(); }
We create a named system Mutex
to synchronize access to the file. We then acquire ownership of the Mutex
before beginning to write the numbers. Finally, after writing to the file, we release the Mutex
.
Now, even if we run this code multiple times simultaneously, we won’t get any interweaving between the different number streams. That’s because we’re only writing to the file while we have ownership of the Mutex
, and that can only be the case with one thread at a time.
Error Handling in Mutex-Protected Code
In our code, we haven’t implemented any error handling. The ReleaseMutex
method will never be called if we encounter an exception in our loop. The next thread to acquire this Mutex
will face an AbandonedMutexException
exception.
Another scenario we haven’t considered is if the Mutex
is in an abandoned state when we try to acquire it.
Let’s solve that:
public static void WriteNumbers(string fileName) { using var mutex = new Mutex(initiallyOwned: false, "Global\\numbers_output"); try { mutex.WaitOne(); } catch (AbandonedMutexException) { File.WriteAllText(fileName, string.Empty); } try { for (var number = 1; number <= 50; number++) { File.AppendAllText(fileName, $"{number} "); Thread.Sleep(100); } } finally { mutex.ReleaseMutex(); } }
Now, if the last owner of the Mutex
abandoned it, we assume that the file is corrupt and clear its contents. We’ll then proceed with writing the numbers as usual.
We also guarantee that we call the ReleaseMutex
method if the loop throws an exception.
Conclusion
With the Mutex
class in C#, we can easily overcome a lot of the different challenges we face when trying to access a shared resource from a multi-threaded environment. We can offload the heavy lifting to the operating system and rely on its guarantees to write safe, performant code that makes the maximum use of all the resources it has available.