Skip to content

Instantly share code, notes, and snippets.

@pmunin
Last active August 6, 2019 20:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pmunin/3f20d1051fb74a09d07fe8bc6310d4a5 to your computer and use it in GitHub Desktop.
Save pmunin/3f20d1051fb74a09d07fe8bc6310d4a5 to your computer and use it in GitHub Desktop.
ReadWriteLockableValue.cs
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
namespace ConcurrentLocking
{
public class ConcurrentLockDictionary<TKey>
{
public class KeyLock
{
public int RefCount;
public TKey Key { get; set; }
public ReaderWriterLockSlim RWLock { get; } = new ReaderWriterLockSlim();
}
public ConcurrentLockDictionary(IEqualityComparer<TKey> comparer = null)
{
this.EqualityComparer = comparer;
}
ConcurrentDictionary<TKey, Lazy<KeyLock>> lockByKey;
public ConcurrentDictionary<TKey, Lazy<KeyLock>> LockByKey
{
get
{
if (lockByKey == null)
{
lockByKey = EqualityComparer == null
? new ConcurrentDictionary<TKey, Lazy<KeyLock>>()
: new ConcurrentDictionary<TKey, Lazy<KeyLock>>(EqualityComparer);
}
return lockByKey;
}
}
public IEqualityComparer<TKey> EqualityComparer { get; }
public IDisposable BeginUsingKey(TKey key, out KeyLock keyLock)
{
var _keyLock = keyLock = LockByKey.GetOrAdd(key, new Lazy<KeyLock>(() => new KeyLock() { Key = key})).Value;
Interlocked.Increment(ref _keyLock.RefCount);
return Disposable.For(() =>
{
lock (_keyLock)
{
_keyLock.RefCount--;
if (_keyLock.RefCount == 0)
{
LockByKey.TryRemove(key, out var __);
_keyLock.RWLock.Dispose();
}
}
});
}
}
}
public static class ReaderWriterLockExtensions
{
public static IDisposable ReadUpgradable(this ReaderWriterLockSlim locker, int? timeoutMs=null, Action onTimeout=null)
{
var successful = true;
if (timeoutMs == null)
{
locker.EnterUpgradeableReadLock();
}
else
{
successful = locker.TryEnterUpgradeableReadLock(timeoutMs.Value);
if (!successful)
{
if (onTimeout != null)
{
onTimeout();
}
else
{
throw new TimeoutException(
"ReaderWriterLockSlim.TryEnterUpgradeableReadLock failed with timeout");
}
}
}
return Disposable.For(() =>
{
if (successful)
{
locker.ExitUpgradeableReadLock();
}
});
}
public static IDisposable Read(this ReaderWriterLockSlim locker, int? timeoutMs = null, Action onTimeout = null)
{
var timeout = false;
if (timeoutMs != null)
{
timeout = !locker.TryEnterReadLock(timeoutMs.Value);
}
else
{
locker.EnterReadLock();
}
if (timeout)
{
if (onTimeout == null)
{
throw new TimeoutException("ReaderWriterLockSlim.TryEnterReadLock failed with timeout");
}
else
{
onTimeout();
}
}
return Disposable.For(() =>
{
if (timeout)
{
locker.ExitReadLock();
}
});
}
public static IDisposable Write(this ReaderWriterLockSlim locker, int? timeoutMs = null, Action onTimeout = null)
{
var timeout = false;
if (timeoutMs != null)
{
timeout = !locker.TryEnterWriteLock(timeoutMs.Value);
}
else
{
locker.EnterWriteLock();
}
if (timeout)
{
if (onTimeout == null)
{
throw new TimeoutException("ReaderWriterLockSlim.TryEnterWriteLock failed with timeout");
}
else
{
onTimeout();
}
}
return Disposable.For(() =>
{
if (timeout)
{
locker.ExitWriteLock();
}
});
}
}
public class ReadWriteLockableValue<T> : IDisposable
{
public ReadWriteLockableValue(T initialValue=default(T), LockRecursionPolicy recursion=LockRecursionPolicy.SupportsRecursion)
{
this.value = initialValue;
this.locker = new ReaderWriterLockSlim(recursion);
}
T value;
ReaderWriterLockSlim locker;
public void Dispose()
{
this.locker.Dispose();
}
public bool Read(Action<T> onRead, bool upgradable=false, TimeSpan? timeout=null)
{
var isTimeout = false;
if (upgradable)
{
if (timeout == null)
{
this.locker.EnterUpgradeableReadLock();
}
else
{
isTimeout = !this.locker.TryEnterUpgradeableReadLock(timeout.Value);
}
}
else
{
if (timeout==null)
{
this.locker.EnterReadLock();
}
else
{
isTimeout = !this.locker.TryEnterReadLock(timeout.Value);
}
}
if (isTimeout) {return false;}
try
{
onRead(this.value);
}
finally
{
if (upgradable)
{
this.locker.ExitUpgradeableReadLock();
}
else
{
this.locker.ExitReadLock();
}
}
return true;
}
public bool Update(Func<T, T> onUpdate, TimeSpan? timeout = null)
{
var isTimeout = false;
if (timeout == null)
{
this.locker.EnterWriteLock();
}
else
{
isTimeout = this.locker.TryEnterWriteLock(timeout.Value);
}
if (isTimeout) {return false;}
try
{
this.value = onUpdate(this.value);
}
finally
{
this.locker.ExitWriteLock();
}
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment