// Each type is own static instance
public static class Locker<T> where T: class
{
/// <summary>
/// Holds resource key, experiation date
/// </summary>
internal static volatile ConcurrentDictionary<string, DateTime> _lockTracker;
private static volatile Object _lockObject = new Object();
static Locker()
{
// Get info for initializing our dict
int numProcs = Environment.ProcessorCount;
int concurrencyLevel = numProcs * 2; // Max operations at once (practically)
int initialCapacity = 13;
_lockTracker = new ConcurrentDictionary<string, DateTime>(concurrencyLevel, initialCapacity);
}
/// <summary>
/// Given a key, report if resource is locked
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static bool IsLocked(string id)
{
DateTime lockExpiration;
if (_lockTracker.TryGetValue(id, out lockExpiration))
return lockExpiration > DateTime.UtcNow;
return false;
}
/// <summary>
/// Given a key, attempt to lock resource for specified amount of time
/// </summary>
/// <param name="id"></param>
/// <param name="secondsToKeep"></param>
/// <returns></returns>
public static bool Lock(string id, int secondsToKeep)
{
DateTime expiration;
// Lock if possible
lock (_lockObject)
{
if (_lockTracker.TryGetValue(id, out expiration) && expiration > DateTime.UtcNow)
return false;
_lockTracker[id] = DateTime.UtcNow.AddSeconds(secondsToKeep);
}
// Cleanup
var keysToRemove = _lockTracker.Keys.Where(key => _lockTracker[key] < DateTime.UtcNow);
foreach (var s in keysToRemove)
{
_lockTracker.TryRemove(s, out expiration);
}
return true;
}
/// <summary>
/// Given a key, release the lock by removing it from tracking
/// </summary>
/// <param name="id"></param>
public static void Release(string id)
{
DateTime expiration;
_lockTracker.TryRemove(id, out expiration);
}
// I don't like a bunch of ToStrings
public static bool IsLocked(int id) => IsLocked(id.ToString());
public static bool Lock(int id, int secondsToKeep) => Lock(id.ToString(), secondsToKeep);
public static void Release(int id) => Release(id.ToString());
}