Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dictionary Update Thread-Safety

Is the following code thread safe?

var dict = new Dictionary<int, string>()
    { { 0, "" }, { 1, "" }, { 2, "" }, { 3, "" } };

var nums = dict.Keys.ToList();

Parallel.ForEach(nums, num =>
            {
                dict[num] = LongTaskToGenerateString();
            });

return dict;
like image 427
innominate227 Avatar asked Oct 30 '13 15:10

innominate227


2 Answers

No, Dictionary<TKey, TValue> class is not thread-safe for modification, as can be seen in the documentation:

A Dictionary<TKey, TValue> can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. In the rare case where an enumeration contends with write accesses, the collection must be locked during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.

In your case, if some threads will finish the LongTaskToGenerateString almost simultaneously their dictionary updates will interference.

You might either use the SyncRoot property to synchronize the access manually, or just take the ConcurrentDictionary<TKey, TValue> class, as suggested by asawyer in the comments.

This implementation suggests that if you're only updating the values for existing keys, it should be OK (looking also at this) - the inelegant effect could be inaccurate value of the version property. It is used to guard against modifying the collection while it's being enumerated, so it doesn't really matter at which value it will end up. Don't know about any guarantees about this though.

like image 149
BartoszKP Avatar answered Nov 12 '22 18:11

BartoszKP


It appears as though your dictionary is only for use as a return value, and is never actually used to look up keys. In that case, you don't even need the dictionary until you've computed all the final output values.

So you can use PLINQ for this:

var nums = new[] { 0, 1, 2, 3 };

var dict = nums.AsParallel()
               .Select(num => new KeyValuePair<int, string>
                                  (num, LongTaskToGenerateString(num)));
               .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

return dict;
like image 4
Gabe Avatar answered Nov 12 '22 18:11

Gabe