Created
November 13, 2020 18:18
-
-
Save aaronhoffman/fb9f354b7e0ccbb630d13fe6ec9c4d20 to your computer and use it in GitHub Desktop.
Async Thread Safe Concurrent Dictionary
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Concurrent; | |
using System.Threading; | |
using System.Threading.Tasks; | |
public class ThreadSafeConcurrentDictionary | |
{ | |
public bool TryGetValue<TValue>(string key, out TValue value) | |
{ | |
return _dictionary.TryGetValue(key, out value); | |
} | |
public TValue GetOrAdd<TValue>(string key, TValue value) | |
{ | |
return GetOrAdd(key, () => value); | |
} | |
public TValue GetOrAdd<TValue>(string key, Func<TValue> valueFactory) | |
{ | |
if (_dictionary.TryGetValue(key, out TValue value)) | |
{ | |
return value; | |
} | |
var lockObject = GetSemaphoreSlim(key); | |
lock (lockObject) | |
{ | |
// double check after lock | |
if (_dictionary.TryGetValue(key, out value)) | |
{ | |
return value; | |
} | |
value = valueFactory(); | |
_dictionary.TryAdd(key, value); | |
} | |
return value; | |
} | |
public async Task<TValue> GetOrAdd<TValue>(string key, Task<TValue> valueTask) | |
{ | |
if (_dictionary.TryGetValue(key, out TValue value)) | |
{ | |
return value; | |
} | |
var semaphoreSlim = GetSemaphoreSlim(key); | |
// async lock | |
await semaphoreSlim.WaitAsync(); | |
try | |
{ | |
// double check after lock | |
if (_dictionary.TryGetValue(key, out value)) | |
{ | |
return value; | |
} | |
value = await valueTask; | |
_dictionary.TryAdd(key, value); | |
} | |
finally | |
{ | |
semaphoreSlim.Release(); | |
} | |
return value; | |
} | |
public TValue AddOrUpdate<TValue>(string key, TValue value) | |
{ | |
return AddOrUpdate(key, () => value); | |
} | |
public TValue AddOrUpdate<TValue>(string key, Func<TValue> valueFactory) | |
{ | |
var lockObject = GetSemaphoreSlim(key); | |
lock (lockObject) | |
{ | |
var value = valueFactory(); | |
var result = (TValue)_dictionary.AddOrUpdate(key, value, (existingKey, existingValue) => value); | |
return result; | |
} | |
} | |
public async Task<TValue> AddOrUpdate<TValue>(string key, Task<TValue> valueTask) | |
{ | |
TValue result; | |
var semaphoreSlim = GetSemaphoreSlim(key); | |
// async lock | |
await semaphoreSlim.WaitAsync(); | |
try | |
{ | |
var value = await valueTask; | |
result = (TValue)_dictionary.AddOrUpdate(key, value, (existingKey, existingValue) => value); | |
} | |
finally | |
{ | |
semaphoreSlim.Release(); | |
} | |
return result; | |
} | |
public object Remove(string key) | |
{ | |
var lockObject = GetSemaphoreSlim(key); | |
lock (lockObject) | |
{ | |
_dictionary.TryRemove(key, out object value); | |
return value; | |
} | |
} | |
/// <summary> | |
/// Retrieve the SemaphoreSlim specific to this cache key. | |
/// This method is thread safe due to its use of Lazy<>. | |
/// </summary> | |
/// <param name="key"></param> | |
/// <returns></returns> | |
private SemaphoreSlim GetSemaphoreSlim(string key) | |
{ | |
var lazySemaphoreSlim = _semaphoreSlimDictionary.GetOrAdd(key, x => new Lazy<SemaphoreSlim>(() => new SemaphoreSlim(1, 1))); | |
return lazySemaphoreSlim.Value; | |
} | |
private readonly ConcurrentDictionary<string, object> _dictionary = new ConcurrentDictionary<string, object>(); | |
/// <summary> | |
/// `GetOrAdd` call on the dictionary is not thread safe and we might end up creating a SemaphoreSlim more than once. | |
/// To prevent this, Lazy<> is used. In the worst case multiple Lazy<> objects are created for multiple | |
/// threads but only one of the objects succeeds in creating a SemaphoreSlim due to use of Lazy<>.Value. | |
/// </summary> | |
private readonly ConcurrentDictionary<string, Lazy<SemaphoreSlim>> _semaphoreSlimDictionary = new ConcurrentDictionary<string, Lazy<SemaphoreSlim>>(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A Thread Safe ConcurrentDictionary that supports async locks.