Using Mutex<T> to synchronize access to a shared resource
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
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.
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.
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:
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!