Search code examples
c#concurrency.net-4.0

Updating fields of values in a ConcurrentDictionary


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.


Solution

  • 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() );
        }
    
    }