I'm using ConcurrentDictionary to cache data with parallel access and sometimes new items can be stored in db and they are not loaded into cache. This is reason why I use GetOrAdd
public User GetUser(int userId)
{
return _user.GetOrAdd(userId, GetUserFromDb);
}
private User GetUserFromDb(int userId)
{
var user = _unitOfWork.UserRepository.GetById(userId);
// if user is null, it is stored to dictionary
return user;
}
But how I can check if user was get from db and store user to dictionary only if user is not null?
Possibly I can remove null from ConcurrentDictionary immediately after GetOrAdd but it doesn't look thread safe and it is not very elegant solution. Useless insert and remove from dictionary. Do you have any idea how to do it?
Also, although all methods of ConcurrentDictionary<TKey,TValue> are thread-safe, not all methods are atomic, specifically GetOrAdd and AddOrUpdate. To prevent unknown code from blocking all threads, the user delegate that's passed to these methods is invoked outside of the dictionary's internal lock.
Represents a thread-safe collection of key/value pairs that can be accessed by multiple threads concurrently.
ConcurrentDictionary is thread-safe collection class to store key/value pairs. It internally uses locking to provide you a thread-safe class. It provides different methods as compared to Dictionary class. We can use TryAdd, TryUpdate, TryRemove, and TryGetValue to do CRUD operations on ConcurrentDictionary.
Here's a hacky solution, I hope something better is possible. Make GetUserFromDb
throw if the user is not found. This aborts the store into the dictionary. Make GetUser
catch the exception. This is using exceptions for control flow which is not nice.
public User GetUser(int userId)
{
var user = _user.GetOrAdd(userId, GetUserFromDb);
if (user == null) _user.TryRemove(userId, out user);
}
You can also wrap that into an extension method:
public static TValue GetOrAddIfNotNull<TKey, TValue>(
this ConcurrentDictionary<TKey, TValue> dictionary,
TKey key,
Func<TKey, TValue> valueFactory) where TValue : class
{
var value = dictionary.GetOrAdd(key, valueFactory);
if (value == null) dictionary.TryRemove(key, out value);
return value;
}
Then your code will look like:
public User GetUser(int userId)
{
var user = _user.GetOrAddIfNotNull(userId, GetUserFromDb)
}
UPDATE
As per @usr comment, there might be a case when:
GetOrAdd
, adds null
to the dictionary and pauses.GetOrAdd
and retrieves null
from the dictionary instead of hitting the database.TryRemove
and remove record from the dictionary.With this timing, Thread 2 will get null
instead of hitting the database and getting the user record. If this edge case matters to you and you still want to use ConcurrentDictionary
, then you can use lock
in the extension method:
public static class ConcurrentDictionaryExtensions
{
private static readonly object myLock = new object();
public static TValue GetOrAddIfNotNull<TKey, TValue>(
this ConcurrentDictionary<TKey, TValue> dictionary,
TKey key,
Func<TKey, TValue> valueFactory) where TValue : class
{
lock (myLock)
{
var value = dictionary.GetOrAdd(key, valueFactory);
if (value == null) dictionary.TryRemove(key, out value);
return value;
}
}
}
I am extending @NikolaiSamteladze solution to include double-checked locking so that other threads can skip acquiring lock after dictionary updation
public static class ConcurrentDictionaryExtensions
{
private static readonly object myLock = new object();
public static TValue GetOrAddIfNotNull<TKey, TValue>(
this ConcurrentDictionary<TKey, TValue> dictionary,
TKey key,
Func<TKey, TValue> valueFactory) where TValue : class
{
TValue value;
if (!dictionary.TryGetValue(key, out value))
{
lock (myLock)
{
value = dictionary.GetOrAdd(key, valueFactory);
if (value == null) dictionary.TryRemove(key, out value);
}
}
return value;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With