Using Mutex<T> to synchronize access to a shared resource

 
 
  • Gérald Barré

When accessing a shared resource, you can use the lock statement or a synchronization primitive such as Mutex. However, in complex code, it's easy to forget to acquire the lock:

C#
var obj = new object();
var value = 42;
lock (obj)
{
    // You need to ensure you use lock everywhere you access the shared resource
    Console.WriteLine(value);
}

// ⚠️ You can access the resource without a lock
value = 43;

You can make it more explicit and less error-prone by creating a Mutex<T> class that encapsulates the shared resource and the synchronization primitive.

C#
var mutex = new Mutex<int>(42);
using (var mutexScope = mutex.Acquire())
{
    // Access the shared resource
    Console.WriteLine(mutexScope.Value);

    // Update the shared resource
    mutexScope.Value = 43;
}

// ✔️ You cannot use the shared resource outside the mutex scope

Note that this is a mitigation, not a fully robust solution. You can still access the shared resource outside the scope by copying the data inside the scope and using it elsewhere:

C#
var mutex = new Mutex<MyData>();
MyData escapedData;
using (var mutexScope = mutex.Acquire())
{
    // Update the shared resource
    escapedData = mutexScope.Value;
}

// ⚠️ You can use the shared resource outside the mutex scope
escapedData.Value = 42;

Here's the implementation of the Mutex<T> class:

C#
sealed class Mutex<T>
{
    internal T _value;
    private readonly Lock _lock = new();

    public Mutex() { _value = default!; }
    public Mutex(T initialValue) => _value = initialValue;

    public MutexScope<T> Acquire()
    {
        _lock.Enter();
        return new MutexScope<T>(this);
    }

    internal void Release() => _lock.Exit();
}

sealed class MutexScope<T> : IDisposable
{
    private readonly Mutex<T> mutex;
    private bool disposed;

    internal MutexScope(Mutex<T> mutex)
    {
        this.mutex = mutex;
    }

    public ref T Value
    {
        get
        {
            ObjectDisposedException.ThrowIf(disposed, this);
            return ref mutex._value!;
        }
    }

    public void Dispose()
    {
        mutex.Release();
        disposed = true;
    }
}

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?