Updating dictionary from multiple threads

Kaushik Kapasi 21 Reputation points
2021-07-19T09:58:20.78+00:00

I have a 2-level nested dictionary:

Dictionary<int, Dictionary<int, string>> MyOriginalDict

I need to update the above string values.
I created a copy of the above dictionary and I iterate through this copy using foreach and for every level-2 key, I call a function which updates the value corresponding to it in the main dictionary.

I use ThreadPoolLoader to call the above function. Around 500 threads are created which independently update the original dictionary using ContainsKey() to update the correct value. No new values are added and no two threads update the same key-value.

There are no errors thrown and function calculation is correct. However, it seems the string values updated in the Original dictionary are randomly assigned. It's like ContainsKey() is not working - the values are updated randomly.

Where am I going wrong? Note: If I do the above in a single thread and simply update each string value in a foreach loop, it all works fine but takes several minutes to run.

Thanks.

C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,929 questions
{count} votes

Accepted answer
  1. AgaveJoe 28,371 Reputation points
    2021-07-20T16:18:58.503+00:00

    Your sample code is using i at the time of execution which is 50. It has to do with closures. Task.Run() is the recommended approach.

        public class MyClass
        {
            ConcurrentDictionary<int, int> _numbers = new ConcurrentDictionary<int, int>();
            public MyClass() { }
            public async Task CallMeAsync()
            {
                _numbers.Clear();
                _numbers.TryAdd(99, 100);
                int threadCount = 50;
    
                Task[] tasks = new Task[threadCount];
    
                for (int i = 0; i < threadCount; i++)
                {
                    int j = i;
                    tasks[j] = Task.Run(() => AddToDict(j));
                }
    
                await Task.WhenAll(tasks);
    
                foreach (KeyValuePair<int, int> item in _numbers)
                    Console.WriteLine("Key: " + item.Key.ToString() + "; Value: " + item.Value.ToString());
    
    
            }
            private void AddToDict(int number)
            {
                if(!_numbers.TryAdd(number, number + 1))
                {
                    Console.WriteLine("Not added - Key {0} Value {1} ", number, number + 1);
                }
            }
        }
    

    Implementation (.NET 5)

        class Program
        {    
            static async Task Main(string[] args)
            {
                MyClass myClass = new MyClass();
                await myClass.CallMeAsync();
            }
        }
    
    0 comments No comments

3 additional answers

Sort by: Most helpful
  1. Kaushik Kapasi 21 Reputation points
    2021-07-20T06:23:00.407+00:00

    I changed my code to use ConcurrentDictionary. No luck.

    I also changed the code to create a new ConcurrentDictionary at the class level and have multiple thread add objects to it. However, still when I look at it after all threads are completed, it has fewer objects than there should be!!! I even use lock{} before adding new KeyValue.

    I think, I am missing something w.r.t. how items are added to this dictionary from simultaneously running multiple threads.


  2. Kaushik Kapasi 21 Reputation points
    2021-07-20T15:10:14.377+00:00

    Here is my Console App (Please copy-paste in new VS console app and run):

    // I create multiple threads in a loop and add values to a ConcurrentDictionary from within each thread. Once all threads
    // have completed, I print the contents of the dictionary to screen - doesn't work!!! How do I make this work?

    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;

    namespace MultThreadTest
    {
    class Program
    {
    static void Main(string[] args)
    {
    MyClass myClass = new MyClass();
    myClass.CallMe();
    }
    }

    public class MyClass
    {
        ConcurrentDictionary<int, int> _numbers = new ConcurrentDictionary<int, int>();
    
        public MyClass(){}
    
    
        public void CallMe()
        {
            _numbers.Clear();
            _numbers.TryAdd(99, 100);
    
            int threadCount = 50;
            var doneEvent = new CountdownEvent(threadCount);
    
            for (int i = 0; i < threadCount; i++)
            {
                Task.Factory.StartNew(() => AddToDict(i));
            }
            Task.WaitAll();
    
            foreach (KeyValuePair<int, int> item in _numbers)
                Console.WriteLine("Key: " + item.Key.ToString() + "; Value: " + item.Value.ToString());
        }
    
        private void AddToDict(int number)
        {
            _numbers.TryAdd(number, number + 1);
        }
    }
    

    }


  3. Kaushik Kapasi 21 Reputation points
    2021-07-20T16:33:56.313+00:00

    WoW! You not only showed the right way to use multi-threading but also corrected careless mistakes. Thanks!

    Q. Why is (int j =i) required?


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.