C# locker

Date: 2019-09-10
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
    }
}
25640cookie-checkC# locker