user3587180
user3587180

Reputation: 1397

Why does the value of a ConcurrentDictionary need to be thread safe?

Consider the below dictionary

var dic = new ConcurrentDictionary<Guid, List<Customer>>();

If I do Parallel.For and add items to that dictionary the results are unpredictable. Sometimes the items in the list are null and sometimes there are exceptions.

var id = Guid.NewGuid();
Parallel.For(0, 1000, _ =>
{
    dic.AddOrUpdate(id,new List<Customer>(), (key, lst) =>
    {
        var c = new Customer()
        {
             Id = key
        };
        lst.Add(c);
        return lst;
    });
});

I understand that List.Add is not thread safe so it's causing the issue. I can fix it by using a ConcurrentBag instead of list.

However, I'm trying to understand why the dictionary's item has to be thread safe when the dictionary is itself thread safe? Doesn't concurrent dictionary already lock when an item is being added or updated?

Upvotes: 0

Views: 713

Answers (2)

Steve
Steve

Reputation: 11963

ConcurrentDictionary's lock wont save you in the following scenario :

Thread1:

List<Customer> c = ConcurrentDictionary[{guid}];
Thread.Sleep(1000);

Thread2:

List<Customer> c2 = ConcurrentDictionary[{same guid}];
c2.Add(...);

Thread1:

c.Add(...);

Upvotes: 0

Chris
Chris

Reputation: 743

From the MSDN documentation here (emphasis mine): https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2.addorupdate?view=netframework-4.7.2

For modifications and write operations to the dictionary, ConcurrentDictionary uses fine-grained locking to ensure thread safety. (Read operations on the dictionary are performed in a lock-free manner.) However, the updateValueFactory delegate is called outside the locks to avoid the problems that can arise from executing unknown code under a lock. Therefore, AddOrUpdate is not atomic with regards to all other operations on the ConcurrentDictionary class.

Upvotes: 2

Related Questions