I'm noticing that GetOrAdd() always executes the factory delegate, even when the value exists in the dictionary. For example:
class Program { private static ConcurrentDictionary<string, string> _cache = new ConcurrentDictionary<string, string>(); static void Main(string[] args) { string value; value = GetValueFromCache("A"); // cache is empty, CacheValueFactory executes, A is added value = GetValueFromCache("A"); // cache contains A, CacheValueFactory executes value = GetValueFromCache("C"); // cache contains A, CacheValueFactory, C is added value = GetValueFromCache("A"); // cache contains A and C, CacheValueFactory executes } private static string GetValueFromCache(string key) { string val = _cache.GetOrAdd(key, CacheValueFactory(key)); return val; } private static string CacheValueFactory(string key) { if (key == "A") return "Apple"; else if (key == "B") return "Banana"; else if (key == "C") return "Cherry"; return null; } }
Upon the first call to GetValueFromCache("A"), the cache is empty and A:Apple is added. Stepping in with the debugger, I noticed that on the second and third calls to GetValueFromCache("A"), the CacheValueFactory() method always executes. Is this expected? I would've thought that the delegate method wouldn't execute if the key exists in the dictionary.
It is thread safe in your usage. It becomes not thread safe when the delegate passed to AddOrUpdate has side effects, because those side effects may be executed twice for the same key and existing value.
The GetOrAdd functionThe vast majority of methods it exposes are thread safe, with the notable exception of one of the GetOrAdd overloads: TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory); This overload takes a key value, and checks whether the key already exists in the database.
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.
tl;dr; To make a ConcurrentDictionary only call a delegate once when using GetOrAdd, store your values as Lazy<T>, and use by calling GetOrAdd (key, valueFactory).Value. The ConcurrentDictionary is a dictionary that allows you to add, fetch and remove items in a thread-safe way .aspx).
(Read operations on the dictionary are performed in a lock-free manner.) However, the valueFactory delegate is called outside the locks to avoid the problems that can arise from executing unknown code under a lock. Therefore, GetOrAdd is not atomic with regards to all other operations on the ConcurrentDictionary<TKey,TValue> class.
The ConcurrentDictionary<T,V> in .NET 4.0 is thread safe but not all methods are atomic. ... not all methods are atomic, specifically GetOrAdd and AddOrUpdate. The user delegate that is passed to these methods is invoked outside of the dictionary's internal lock.
While valueFactory is generating the value, a different thread inserts a value for the key. After valueFactory executes and upon rechecking for the key, the key inserted by the other thread is found. The value inserted by the other thread is returned. Adds a key/value pair to the ConcurrentDictionary<TKey,TValue> if the key does not already exist.
The reason you're seeing this is that you are not passing CacheValueFactory
as a delegate but instead evaluating the function promptly and passing the resulting value. This causes you to use the overload which accepts a key and value and not the one which accepts a key and delegate.
To use the delegate version switch the code to the following
string val = _cache.GetOrAdd(key, CacheValueFactory);
If you want to handle slightly more complicated scenarios, for example, when the parameter doesn't match the key, you could use a lambda.
var keyStr = string.Format("Something_{0}", key); string val = _cache.GetOrAdd(keyStr,_ => CacheValueFactory(key));
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