17

I am trying to update entries in a ConcurrentDictionary something like this:

class Class1
{
    public int Counter { get; set; }
}

class Test
{
    private ConcurrentDictionary<int, Class1> dict =
        new ConcurrentDictionary<int, Class1>();

    public void TestIt()
    {
        foreach (var foo in dict)
        {
            foo.Value.Counter = foo.Value.Counter + 1; // Simplified example
        }
    }
}

Essentially I need to iterate over the dictionary and update a field on each Value. I understand from the documentation that I need to avoid using the Value property. Instead I think I need to use TryUpdate except that I don’t want to replace my whole object. Instead, I want to update a field on the object.

After reading this blog entry on the PFX team blog: Perhaps I need to use AddOrUpdate and simply do nothing in the add delegate.

Does anyone have any insight as to how to do this?


I have tens of thousands of objects in the dictionary which I need to update every thirty seconds or so. Creating new ones in order to update the property is probably not feasible. I would need to clone the existing object, update it and replace the one in the dictionary. I’d also need to lock it for the duration of the clone/add cycle. Yuck.

What I’d like to do is iterate over the objects and update the Counter property directly if possible.

My latest research has led me to to Parallel.ForEach which sounds great but it is not supposed to be used for actions that update state.

I also saw mention of Interlocked.Increment which sounds great but I still need to figure out how to use it on each element in my dictionary in a thread safe way.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
rboarman
  • 8,248
  • 8
  • 57
  • 87
  • is there actually any concurrency going on if you only ever update a field of each value every 30 seconds? Sounds like a normal dictionary and a foreach loop should be all you need. Parallel.ForEach should work, too, since there are no shared resources. – dtb May 25 '10 at 01:17
  • While the update thread is running, additional objects will be added to and removed from the dictionary from other threads. In addition, other properties on the object will be updated via yet other threads in response to WCF messages. So yes, there is a lot of concurrency going on. Thanks for your help! – rboarman May 25 '10 at 03:12
  • Alright. Then my answer persists. Use AddOrUpdate/GetOrAdd/TryUpdate etc to manipulate the contents of the dictionary; take a snapshot of the dictionary when iterating it; lock the individual Class1 instances when changing their properties. – dtb May 25 '10 at 23:43

3 Answers3

16

First, to solve your locking problem:

class Class1
{
    // this must be a variable so that we can pass it by ref into Interlocked.Increment.
    private int counter;

    public int Counter
    {
        get{return counter; }
    }

    public void Increment()
    {
        // this is about as thread safe as you can get.
        // From MSDN: Increments a specified variable and stores the result, as an atomic operation.
        Interlocked.Increment(ref counter);

        // you can return the result of Increment if you want the new value,
        //but DO NOT set the counter to the result :[i.e. counter = Interlocked.Increment(ref counter);] This will break the atomicity.
    }
}

Iterating the just values should be faster than iterating the key value pair. [Though I think iterating a list of keys and doing the look-ups will be faster still on the ConcurrentDictionary in most situations.]

class Test
{
    private ConcurrentDictionary<int, Class1> dictionary = new ConcurrentDictionary<int, Class1>();

    public void TestIt()
    {
        foreach (var foo in dictionary.Values)
        {
            foo.Increment();
        }
    }

    public void TestItParallel()
    {
        Parallel.ForEach(dictionary.Values,x=>x.Increment() );
    }

}
Jason Hernandez
  • 2,907
  • 1
  • 19
  • 30
5

ConcurrentDictionary doesn't help you with accessing members of stored values concurrently, just with the elements themselves.

If multiple threads call TestIt, you should get a snapshot of the collection and lock the shared resources (which are the individual dictionary values):

foreach (KeyValuePair<int, Class1> kvp in dict.ToArray())
{
    Class1 value = kvp.Value;
    lock (value)
    {
        value.Counter = value.Counter + 1;
    }
}

However, if you want to update the counter for a specific key, ConcurrentDictionary can help you with atomically adding a new key value pair if the key does not exist:

Class1 value = dict.GetOrAdd(42, key => new Class1());
lock (value)
{
    value.Counter = value.Counter + 1;
}

AddOrUpdate and TryUpdate indeed are for cases in which you want to replace the value for a given key in a ConcurrentDictionary. But, as you said, you don't want to change the value, you want to change a property of the value.

dtb
  • 213,145
  • 36
  • 401
  • 431
  • Another solution from the folks at MS was posted here: http://social.msdn.microsoft.com/Forums/en-US/parallelextensions/thread/64f0c314-e3eb-457a-9dad-14d13cf06d68 I'll try both your suggestions and theirs and report back. Thank you again. – rboarman May 26 '10 at 18:06
2

You can use the AddOrUpdate function.

Here is how you can increment the current value by 1:

dict.AddOrUpdate(key, 1, (key, oldValue) => oldValue + 1);
  • 2
    This is not thread safe. See the remarks https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2.addorupdate. The ValueFactory has no locks so race conditions could occur – Darem Aug 21 '20 at 06:17