Last active
May 9, 2021 10:58
-
-
Save jRimbault/d2640e9d8ff3b998d66fbc4a57cf7e0b to your computer and use it in GitHub Desktop.
a RAII mutex lock using the `IDisposable` interface
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Threading; | |
namespace Sync | |
{ | |
// If C# had different (slightly better imo) visibility semantics | |
// that interface wouldn't exists and the nested type wouldn't need to be nested. | |
// The "best" we can do is use the repo version, put it in its own project and use | |
// the `internal` visibility, which is dumb shit. | |
public interface IMutexGuard<T> : IDisposable | |
where T : notnull | |
{ | |
public T Value { get; } | |
} | |
public sealed class Mutex<T> | |
where T : notnull | |
{ | |
private readonly object _monitor = new object(); | |
private readonly T _inner; | |
public Locker(T value) => _inner = value; | |
public IMutexGuard<T> Lock() | |
{ | |
if (!Monitor.TryEnter(_monitor, Timeout.Infinite)) | |
{ | |
throw new Exception($"Failed to acquire lock for {typeof(T)}"); | |
} | |
return new MutexGuard<T>(this); | |
} | |
private void Unlock() | |
{ | |
Monitor.Exit(_monitor); | |
} | |
private sealed class MutexGuard<U> : IMutexGuard<U> | |
where U : notnull | |
{ | |
public U Value { get => _parent._inner; } | |
private bool _isDisposed = false; | |
private readonly Mutex<U> _parent; | |
public MutexGuard(Locker<U> parent) => _parent = parent; | |
public void Dispose() | |
{ | |
if (_isDisposed) | |
{ | |
return; | |
} | |
_parent.Unlock(); | |
_isDisposed = true; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Sync; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Threading.Tasks; | |
Mutex<List<(int, int)>> list = new(new()); | |
// without the lock we'd get a concurrency exception : | |
// System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.' | |
Task.WaitAll(Task.Run(() => | |
{ | |
using var guard = list.Lock(); | |
foreach (var i in Enumerable.Range(1, 2000)) | |
{ | |
guard.Value.Add((i, i)); | |
} | |
}), | |
Task.Run(() => | |
{ | |
using var guard = list.Lock(); | |
foreach (var pair in guard.Value) | |
{ | |
Console.WriteLine(pair); | |
} | |
})); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment