KeyedLock
Fri Jun 10 2022 04:36:02 GMT+0000 (Coordinated Universal Time)
using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace AppsensPro.Infrastructure.Threading { public sealed class KeyedLock<TKey> { /// <summary> /// Simple wrapper to allow counting how many references an object has /// </summary> /// <typeparam name="T"></typeparam> private sealed class RefCounted<T> { public RefCounted(T value) { RefCount = 1; Value = value; } public int RefCount { get; set; } public T Value { get; private set; } } /// <summary> /// Internally maintained dictionary of <see cref="SemaphoreSlim"/> /// </summary> private readonly Dictionary<TKey, RefCounted<SemaphoreSlim>> _semaphoreSlims = new Dictionary<TKey, RefCounted<SemaphoreSlim>>(); /// <summary> /// Atomically get or create a value, with the associated <see cref="RefCounted{T}"/> incremented /// </summary> /// <param name="key"></param> /// <returns></returns> private SemaphoreSlim GetOrCreate(TKey key) { RefCounted<SemaphoreSlim> item; lock (_semaphoreSlims) { if (_semaphoreSlims.TryGetValue(key, out item)) { ++item.RefCount; } else { item = new RefCounted<SemaphoreSlim>(new SemaphoreSlim(1, 1)); _semaphoreSlims[key] = item; } } return item.Value; } /// <summary> /// Lock with specified key, returning a disposable <see cref="Releaser"/> /// </summary> /// <param name="key"></param> /// <returns></returns> public IDisposable Lock(TKey key) { GetOrCreate(key).Wait(); return new Releaser(key, _semaphoreSlims); } /// <summary> /// Asynchronously lock with specified key, returning a disposable <see cref="Releaser"/> /// </summary> /// <param name="key"></param> /// <returns></returns> public async Task<IDisposable> LockAsync(TKey key) { await GetOrCreate(key).WaitAsync().ConfigureAwait(false); return new Releaser(key, _semaphoreSlims); } /// <summary> /// The returned Disposable <see cref="Releaser"/>. /// The lock will only be removed from dictionary if exists no reference left /// </summary> private sealed class Releaser : IDisposable { private TKey _key; private readonly Dictionary<TKey, RefCounted<SemaphoreSlim>> _locks; public Releaser(TKey key, Dictionary<TKey, RefCounted<SemaphoreSlim>> locks) { _key = key; _locks = locks; } public void Dispose() { RefCounted<SemaphoreSlim> item; lock (_locks) { item = _locks[_key]; --item.RefCount; if (item.RefCount == 0) _locks.Remove(_key); } item.Value.Release(); } } } }
Comments