I abstracted the Interlocked.CompareExchange
away and used a collection of interfaces.
ILockManager<T>
: Contains all individual locks for each Enum
ILock
: The lock for an individual Enum
ILockReleaser
: Responsible for releasing the lock when it is acquired
Interfaces
public interface ILockManager<T> where T : Enum {
bool TryAcquire(T key, out ILockReleaser lockReleaser);
}
public interface ILock {
string Name { get; }
bool TryAcquire(out ILockReleaser lockReleaser);
}
public interface ILockReleaser {
void Release();
}
Example
You would use this the following (just like a normal Interlock):
enum FooLocks {
FooOne,
FooTwo,
}
public class FooClass {
private readonly ILockManager<FooLocks> _lockManager;
public FooClass(ILockManager<FooLocks> lockManager) {
_lockManager = lockManager;
}
public void RunByOneThreadOnly() {
if (_lockManager.TryAcquire(FooLocks.FooOne, out var activeLock))
return;
try {
/* Your code */
} finally {
activeLock.Release();
}
}
}
Implementation
Here is the implementation of the interfaces for ILockManager<T>
, ILock
and ILockReleaser
:
public class LockManager<T> : ILockManager<T> where T : Enum {
public bool TryAcquire(T key, out ILockReleaser lockReleaser) => _locks[key].TryAcquire(out lockReleaser);
// Create a lock for each enum value
private static readonly IReadOnlyDictionary<T, ILock> _locks = new Dictionary<T, ILock>((Enum.GetValues(typeof(T)) as T[]).Select(e => new KeyValuePair<T, ILock>(e, new Lock(e.ToString()))));
}
public class Lock : ILock, ILockReleaser {
public string Name { get; }
public Lock(string name) {
Name = name;
}
public bool TryAcquire(out ILockReleaser lockReleaser) {
lockReleaser = this;
return Interlocked.CompareExchange(ref _curLock, 1, 0) != 0;
}
public void Release() {
Interlocked.Exchange(ref _curLock, 0);
}
public override string ToString() => Name;
private int _curLock;
}
Remarks
This does not support flagged Enums such as FooLocks.FooOne | FooLocks.FooTwo
. it would be possible to support them, however, I think that would abstract too much and I would leave it at the consuming class to handle flagged enums.
int
and casting) is basically exactly what I do.enum
but cast it before you do the exchange.int
to use it asref
in the call. You can't cast during aref
.CompareExchange
.