ZetCode

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.

Program.cs
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.

Program.cs
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.

Program.cs
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.

Program.cs
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.

Program.cs
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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all C# tutorials.