C# Mutex
last modified October 17, 2026
In this article we show how to use the Mutex primitive to
synchronize threads and processes in C#.
A thread is a unique execution path of a program. When several threads work with the same resource at the same time, the resource can end up in an inconsistent state. To prevent this, we need a synchronization mechanism that grants access to the shared resource to one thread at a time.
The section of code that accesses the shared resource is called a critical section. The resource can be a data structure, a file, or a device.
Mutex
A mutex (mutual exclusion) is a synchronization primitive that
grants exclusive access to a shared resource to only one thread. Other threads
that want to access the resource are blocked until the thread holding the mutex
releases it. The Mutex class is located in the
System.Threading namespace.
A thread acquires a mutex with WaitOne and releases it with
ReleaseMutex. A mutex has thread affinity: only the thread
that acquired the mutex can release it. A thread that already owns a mutex can
call WaitOne again without blocking, but it must then call
ReleaseMutex the same number of times.
A mutex is similar to the lock statement, but it can also be
named. A named mutex is visible to the operating system and can
synchronize threads in different processes. This makes it much slower than
lock. Within a single process, prefer the lock
statement; use Mutex when you need to coordinate several
processes.
C# Mutex simple example
The following is a simple example that uses Mutex.
var mutex = new Mutex();
var threads = new List<Thread>();
for (int i = 0; i < 4; i++)
{
var t = new Thread(() => UseResource(mutex))
{
Name = $"Thread {i + 1}"
};
threads.Add(t);
t.Start();
}
foreach (var t in threads)
{
t.Join();
}
Console.WriteLine("all threads finished");
static void UseResource(Mutex mutex)
{
mutex.WaitOne();
try
{
string? name = Thread.CurrentThread.Name;
Console.WriteLine($"{name} has entered protected section");
Thread.Sleep(800);
Console.WriteLine($"{name} has left protected section");
}
finally
{
mutex.ReleaseMutex();
}
}
In the program, we create four threads that access a shared resource. Each of the threads has exclusive access to the resource.
var mutex = new Mutex();
We create an unnamed mutex. It is local to the process. Its initial state is
not owned, so the first thread that calls WaitOne acquires it.
mutex.WaitOne();
try
{
string? name = Thread.CurrentThread.Name;
Console.WriteLine($"{name} has entered protected section");
Thread.Sleep(800);
Console.WriteLine($"{name} has left protected section");
}
finally
{
mutex.ReleaseMutex();
}
This is the critical section of the code that is protected by the mutex. We
release the mutex in the finally block, so it is released even if
the protected code throws an exception.
for (int i = 0; i < 4; i++)
{
var t = new Thread(() => UseResource(mutex))
{
Name = $"Thread {i + 1}"
};
threads.Add(t);
t.Start();
}
In a for loop, we create and start four named threads.
foreach (var t in threads)
{
t.Join();
}
With Join, the main thread waits until all worker threads finish.
$ dotnet run Thread 1 has entered protected section Thread 1 has left protected section Thread 2 has entered protected section Thread 2 has left protected section Thread 3 has entered protected section Thread 3 has left protected section Thread 4 has entered protected section Thread 4 has left protected section all threads finished
The threads never overlap inside the protected section. The order in which they acquire the mutex is not guaranteed.
C# Mutex abandoned
If a thread terminates while it owns a mutex, the mutex is said to be
abandoned. The next thread that acquires it gets an
AbandonedMutexException. The exception is a warning: the waiting
thread does own the mutex, but the data protected by it may be in an
inconsistent state.
using var mutex = new Mutex();
var t = new Thread(() =>
{
mutex.WaitOne();
Console.WriteLine("worker acquired the mutex and exits without releasing it");
});
t.Start();
t.Join();
try
{
mutex.WaitOne();
Console.WriteLine("main thread acquired the mutex");
}
catch (AbandonedMutexException e)
{
Console.WriteLine($"mutex was abandoned: {e.Message}");
Console.WriteLine("main thread now owns the mutex");
}
finally
{
mutex.ReleaseMutex();
}
The worker thread acquires the mutex and finishes without releasing it. When
the main thread calls WaitOne, the exception is thrown. Since the
main thread owns the mutex in both cases, it releases it in the
finally block.
$ dotnet run worker acquired the mutex and exits without releasing it mutex was abandoned: The wait completed due to an abandoned mutex. main thread now owns the mutex
C# Mutex and async code
Because of thread affinity, a Mutex must not be held across an
await. The code after the await may continue on a
different thread, which does not own the mutex.
using var mutex = new Mutex();
mutex.WaitOne();
Console.WriteLine($"acquired on thread {Environment.CurrentManagedThreadId}");
await Task.Delay(100);
Console.WriteLine($"releasing on thread {Environment.CurrentManagedThreadId}");
try
{
mutex.ReleaseMutex();
}
catch (ApplicationException e)
{
Console.WriteLine($"{e.GetType().Name}: {e.Message}");
}
The mutex is acquired on one thread and the release is attempted on another.
$ dotnet run acquired on thread 1 releasing on thread 5 ApplicationException: Object synchronization method was called from an unsynchronized block of code.
For asynchronous code within a process, use SemaphoreSlim and its
WaitAsync method instead.
C# Mutex single instance
In the following example, we create a single instance application. Only one instance of the application is allowed to run on the computer.
To create a single-instance application, we use a named mutex. In order for the
mutex to apply to all terminal sessions, the name of the mutex is prefixed with
Global\. Without a prefix, or with the Local\ prefix,
the mutex is visible only in the current session.
using var mutex = new Mutex(false, @"Global\MySingletonApp");
bool acquired = false;
try
{
acquired = mutex.WaitOne(TimeSpan.Zero);
}
catch (AbandonedMutexException)
{
acquired = true;
}
if (!acquired)
{
Console.WriteLine("application is already running");
return;
}
try
{
Console.WriteLine("application started; press Enter to quit");
Console.ReadLine();
}
finally
{
mutex.ReleaseMutex();
}
We create a global named mutex. The first instance acquires it; other instances fail to acquire it and exit. Another instance of the program can run only after the running application finishes.
using var mutex = new Mutex(false, @"Global\MySingletonApp");
The first parameter tells whether the calling thread should get the initial
ownership of the mutex. We pass false and acquire the mutex
explicitly with WaitOne.
try
{
acquired = mutex.WaitOne(TimeSpan.Zero);
}
catch (AbandonedMutexException)
{
acquired = true;
}
With a zero timeout, WaitOne returns immediately. It returns
true if the mutex was acquired and false if another
process holds it. If a previous instance crashed without releasing the mutex,
we get an AbandonedMutexException; in that case we own the mutex
and can continue.
Run the application in two different terminals.
$ dotnet run application started; press Enter to quit
$ dotnet run application is already running
C# Mutex with NamedWaitHandleOptions
Since .NET 10, the access to a named mutex can be limited with
NamedWaitHandleOptions. The CurrentUserOnly option
restricts the mutex to processes of the current user, which prevents other users
from opening or squatting it. The CurrentSessionOnly option
controls whether the mutex is limited to the current session; setting it to
false has the same effect as the Global\ prefix.
var options = new NamedWaitHandleOptions
{
CurrentUserOnly = true,
CurrentSessionOnly = false
};
using var mutex = new Mutex(true, "MySingletonApp", options, out bool createdNew);
if (!createdNew)
{
Console.WriteLine("application is already running");
return;
}
try
{
Console.WriteLine("application started; press Enter to quit");
Console.ReadLine();
}
finally
{
mutex.ReleaseMutex();
}
We request initial ownership of the mutex. The createdNew output
parameter tells whether the mutex was newly created; if it already existed,
the calling thread does not get its ownership and another instance is running.
Source
Mutex class - language reference
In this article we have used the Mutex primitive to synchronize
access to a protected resource in C#.
Author
List all C# tutorials.