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

 
 
  • Gérald Barré

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

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 100% robust solution. Indeed, you can still access the shared resource outside the scope if you want to. For instance, you can copy the data inside the scope and use 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?Buy Me A Coffee💖 Sponsor on GitHub