Could there be concurrency issues if I update other keys during a key update inside C# ConcurrentDictionary?

49 Views Asked by At

In C# 7, I am making a call to a concurrent dictionary with "GetOrAdd". I have a composite key, say something like "Key1,Part-key1". Inside the value factory I can retrieve the whole tree under the "Key1", meaning that I can load the rest of the "part-keyN" keys. I am doing that because the consumer of the dictionary only require the part-key but when loading it remotely I get the whole document, so I'm optimistically loading it for subsequent calls.

My question is, if I update other keys inside the ConcurrentDictionary, would there be a time in which I might end up in a deadlock or something similar?

var cd = new ConcurrentDictionary<Tuple<string,string>,Task<string>>();
cd.GetOrAdd(Tuple.Create("Key1","Part1"), async (k)=>{
    var fullObject = await retrieveObject(k.Item1);
    foreach(var prop in fullObject.Properties){
        cd.TryAdd(Tuple.Create(k.Item1,prop.Key),prop.Value);
    }
    return fullObject.Properties.Find(prop => prop.Key == k.Item2).Value;
});

A Composite key for me its just a tuple that goes in the key field of the dictionary.

Value factory

1

There are 1 best solutions below

0
Theodor Zoulias On

No, there is no risk of a deadlock. The risk is adding inconsistent entries in the dictionary.

There is no guarantee that the valueFactory will be called only once per key. In case two threads call GetOrAdd concurrently, each thread will invoke independently the valueFactory, and each thread will produce a different TValue. Only one of the TValues will be added in the dictionary though. Both GetOrAdd calls will return the same value, which will be the value that won the race to be added in the dictionary. The other value will be silently discarded.

In your case the valueFactory has side-effects, and these side-effects will persist for all invocations, even for those that will lose the race to add their result in the dictionary. The side-effects are the entries that are added in the dictionary with the TryAdd. Let's imagine that the fullObjects returned by the two concurrent invocations are not identical. They may have different number of Properties, and the common properties might have different values. It is possible that only half of each fullObject's properties will be added in the dictionary, because they will be racing to overwrite each other's properties. Most likely this is going to emerge as a bug in your application, in one way or another.

In case you are interested for GetOrAddAsync implementations that guarantee that the valueFactory is invoked only once per key, you could look at this question: ConcurrentDictionary GetOrAdd async.