Question

I use a ConcurrentDictioanry<string, HashSet<string>> to access some data across many threads.

I read in this article (scroll down) that the method AddOrUpdate is not executed in the lock, so it could endanger thread-safety.

My code is as follows:

//keys and bar are not the concern here
ConcurrentDictioanry<string, HashSet<string>> foo = new ...;
foreach(var key in keys) {
    foo.AddOrUpdate(key, new HashSet<string> { bar }, (key, val) => {
        val.Add(bar);
        return val;
    });
}

Should I enclose the AddOrUpdate call in a lock statement in order to be sure everything is thread-safe?

Was it helpful?

Solution

Locking during AddOrUpdate on its own wouldn't help - you'd still have to lock every time you read from the set.

If you're going to treat this collection as thread-safe, you really need the values to be thread-safe too. You need a ConcurrentSet, ideally. Now that doesn't exist within the framework (unless I've missed something) but you could probably create your own ConcurrentSet<T> which used a ConcurrentDictionary<T, int> (or whatever TValue you like) as its underlying data structure. Basically you'd ignore the value within the dictionary, and just treat the presence of the key as the important part.

You don't need to implement everything within ISet<T> - just the bits you actually need.

You'd then create a ConcurrentDictionary<string, ConcurrentSet<string>> in your application code, and you're away - no need for locking.

OTHER TIPS

You'll need to fix this code, it creates a lot of garbage. You create a new HashSet even if none is required. Use the other overload, the one that accepts the valueFactory delegate. So the HashSet is only created when the key isn't yet present in the dictionary.

The valueFactory might be called multiple times if multiple threads concurrently try to add the same value of key and it is not present. Very low odds but not zero. Only one of these hashsets will be used. Not a problem, creating the HashSet has no side effects that could cause threading trouble, the extra copies just get garbage collected.

The article states that the add delegate is not executed in the dictionary's lock, and that the element you get might not be the element created in that thread by the add delegate. That's not a thread safety issue; the dictionary's state will be consistent and all callers will get the same instance, even if a different instance was created for each of them (and all but one get dropped).

Seems the better answer would be to use Lazy, per this article on the methods that pass in a delegate.

Also another good article Here on Lazy loading the add delegate.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top