using System; using System.Collections.Concurrent; using System.Threading; public class Locker : IDisposable { private static readonly ConcurrentDictionary<string, SemaphoreSlim> keyLocks = new ConcurrentDictionary<string, SemaphoreSlim>(); private readonly string _key; public Locker(string key) : this(key, TimeSpan.FromMinutes(1)) { } public Locker(string key, TimeSpan timeout) { _key = key; var keyLock = keyLocks.GetOrAdd(key, new SemaphoreSlim(1, 1)); if (!keyLock.Wait(timeout)) throw new Exception($"Failed to get lock for key: {key}"); } public void Dispose() { if (keyLocks.TryGetValue(_key, out SemaphoreSlim keyLock)) keyLock.Release(); } } // usage: var key = "InvoiceCache"; using (new Locker(key)) { var cacheValue = ObjectCache.GetCacheItem(key)?.Value; if (cacheValue != null) return (T)cacheValue; T result = await generator().ConfigureAwait(false); // Configure await false ensures the function throws errors var cacheItem = new CacheItem(key, result); var policy = new CacheItemPolicy { AbsoluteExpiration = DateTimeOffset.Now.Add(validTime) }; ObjectCache.Add(cacheItem, policy); return result; }
With cleanup:
using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; namespace Domain.Helpers { public class SemaphoreItem { public SemaphoreItem(string key) { Key = key; } public string Key { get; private set; } public DateTime LastUsed = DateTime.Now; public SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1); } public class Locker : IDisposable { private static readonly ConcurrentDictionary<string, SemaphoreItem> locks = new ConcurrentDictionary<string, SemaphoreItem>(); private static DateTime _lastCleanUp = DateTime.Now; public static Locker GetLock(string key) { var semaphoreItem = locks.GetOrAdd(key, (string x) => new SemaphoreItem(key)); semaphoreItem.LastUsed = DateTime.Now; var success = semaphoreItem.Semaphore.Wait(0); return new Locker(semaphoreItem, success); } public static async Task<Locker> WaitForLock(string key, TimeSpan timeout) { var semaphoreItem = locks.GetOrAdd(key, (string x) => new SemaphoreItem(key)); semaphoreItem.LastUsed = DateTime.Now; var success = await semaphoreItem.Semaphore.WaitAsync(timeout); return new Locker(semaphoreItem, success); } private readonly SemaphoreItem SemaphoreItem; public bool HasLock { get; } private Locker(SemaphoreItem semaphoreItem, bool hasLock) { SemaphoreItem = semaphoreItem; HasLock = hasLock; } public void CleanUp() { if (_lastCleanUp.AddMinutes(10) > DateTime.Now) return; _lastCleanUp = DateTime.Now; foreach (var item in locks) { if (item.Value.Semaphore.CurrentCount == 0) { item.Value.LastUsed = DateTime.Now; continue; // semaphore still in use } if (item.Value.LastUsed.AddMinutes(10) > DateTime.Now) continue; locks.TryRemove(item.Key, out SemaphoreItem _); item.Value.Semaphore.Dispose(); } } #region IDisposable Support private bool disposedValue = false; protected virtual void Dispose(bool disposing) { if (disposedValue) return; disposedValue = true; if (!disposing) return; try { SemaphoreItem.Semaphore.Release(); SemaphoreItem.LastUsed = DateTime.Now; } catch (ObjectDisposedException) { } // ignore CleanUp(); } public void Dispose() => Dispose(true); #endregion } }
256400cookie-checkC# locker