Is it reasonable to use a ConcurrentDictionary<String, SemaphoreSlim>
with thousands or even millions of entries to lock only on specific keys? That is, something like
private static readonly ConcurrentDictionary<String, SemaphoreSlim> _Locks = new();
...
var _Lock = _Locks.GetOrAdd(_Key, (_) => new SemaphoreSlim(1, 1));
await _Lock.WaitAsync();
try { ... } finally { _Lock.Release() }
My main concerns would be:
- the sheer number of SemaphoreSlims that are potentially in play (thousands or even millions)
(_) => new SemaphoreSlim(1, 1)
potentially being called extra times such that there are SemaphoreSlims that are allocated but ultimately never used.
Update with further context:
I reality, I probably only need to support between 1k - 10k entries.
I am trying to use the SemaphoreSlim
s to lock on updates to another ConcurrentDictionary that acts as a cache by the same key.
private static readonly ConcurrentDictionary<String, SemaphoreSlim>
_Locks = new();
private static readonly ConcurrentDictionary<String, ImmutableType> _Cache = new();
...
var _Value;
var _Lock = _Locks.GetOrAdd(_Key, (_) => new SemaphoreSlim(1, 1));
await _Lock.WaitAsync();
try
{
if(!_Cache.TryGetValue(_Key, out _Value) || _Value.ExpirationTime < DateTime.UtcNow)
{
//do expensive operation to construct the _Value
//possibly return from the method if we can't construct the _Value
//(we can't use a Lazy Task - we are in the middle of a bi-direction gRPC call on the server side)
_Cache[_Key] = _Value;
}
} finally { _Lock.Release() }
Note that the _Value
type is an immutable class, we are just trying to avoid blocking other callers for other keys while refreshing our cache for the key in question.
Also note that I am not worried about evicting stale entries. We refresh them as needed but never remove them.