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