0

Made small example to choose best scenario for frequent Dictionary update in multithread enviroment. Then have observed "strange" iteration behavior.

using System;
using System.Collections.Generic;
using System.Threading;

namespace DictionaryTest
{
    class Program
    {
        static int _n;
        static Dictionary<int, string> _dict = new Dictionary<int, string>();

        static void Main(string[] args) {
            for (int i = 0; i < 50; i++) _dict[i] = "FIRST";

            new Thread(ReadDict).Start();
            Thread.Sleep(30);

            // CASE A, Throws exception AS EXPECTED.
            //_dict.Clear();

            // CASE B, ReadDict continues iterate on old dictionary values!!!???
            //_dict = new Dictionary<int, string>();

            // CASE C
            UpdateDict();
            ReadDict();

            Console.ReadKey();
        }

        private static void ReadDict() {
            // Read Method X
            // (continues iterate on old dictionary values!!!???)
            //
            foreach (var kvp in _dict) {
                Thread.Sleep(3);
                Console.WriteLine("{0,3} {1,4} {2,6} :{3,5} Method X", Interlocked.Increment(ref _n), kvp.Key, kvp.Value, Thread.CurrentThread.ManagedThreadId);
            }

            // Read Method Y
            //
            //for (int i = 0; i < 50; i++) {
            //    Thread.Sleep(3);
            //    Console.WriteLine("{0,3} {1,4} {2,6} :{3,5} Method Y", Interlocked.Increment(ref _n), i, _dict[i], Thread.CurrentThread.ManagedThreadId);
            //}
        }

        private static void UpdateDict() {
            var tmp = new Dictionary<int, string>();
            for (int i = 0; i < 50; i++) tmp[i] = "SECOND";
            _dict = new Dictionary<int, string>(tmp);
        }
    }
}

Combinations:

  1. CASE A and (Method X or Method Y) - throws Exception as Expected!
  2. CASE B and Method X - continues iterate on old dictionary values???
  3. CASE C and Method X - continues iterate on old dictionary values???
  4. CASE C and Method Y - iterate only Updated values as Expected!

Does foreach loop take some kind of internal snapshot of static Dictionary member?? If is it so, CASE A also should work but it does not.
Can someone explain what cause this strange behavior?

Edit:

Despite of use of ConcurrentDictionary and code locking, basically my question is about differences between dictionary update methods: to assigne new copy of dictionary as whole new object or better iterate over some collection and update new values seperatly with dict[key]=myObject method? I do not need to keep references to value objects, just replace its.

Arvis
  • 8,273
  • 5
  • 33
  • 46
  • 1
    The simple answer is, your code isn't thread safe. If you want to write thread safe code, you should really be using a lock block around the ReadDict and UpdateDict method contents. Otherwise, behavior is really unpredictable (http://msdn.microsoft.com/en-us/library/c5kehkcz.aspx). – dcp Oct 08 '13 at 15:08

1 Answers1

2

Dictionaries are NOT thread safe. For thread safe dictionaries, use

ConcurrentDictionary<int, string>

It is available from namespace System.Collections.Concurrent;

Please see the following excellent tutorial on concurrent dictionaries.

http://arbel.net/2013/02/03/best-practices-for-using-concurrentdictionary/

EDIT:

I believe that part of the issue is that even with a concurrent dictionary, you're still not thread safe because your update method is NOT using the concurrent dictionary thread-safe maniupulation methods. The following respects the lock on the collection.

private static void UpdateDict()
{
    for (int i = 0; i < 50; i++)
    {
         _dict.AddOrUpdate(i, _ => "SECOND", (i1, s) => "SECOND");
    }    
}

You should find that this preserves the dictionary. I had a typo on the indexer. This now properly reads as you would expect it to.

David L
  • 32,885
  • 8
  • 62
  • 93
  • I don't think that's enough here. He's also making a copy of the dictionary in UpdateDict, so if two threads hit that code at the same time, you're likely going to have problems. If he were just working with a single dictionary, your solution might be ok. That's why I suggested a lock block (refer to comments on OP's question). – dcp Oct 08 '13 at 15:11
  • You can still use locks in conjunction with a concurrent dictionary and it provides plenty of flexibility in specifying the level of concurrent protection. Finally, as per the article - "Writing to the dictionary uses multiple lock objects (also referred to as fine-grained locking) which considerably reduces the chance of contention." – David L Oct 08 '13 at 15:15
  • ok, let's say i take an advantage of ConcurrentDictionary and make use of it, but in any case to be certain if Read block is iterate on new values i must use locker on that block anyway? I was just surprised than multiple simultaneous thread can read different data form same static member. – Arvis Oct 08 '13 at 15:30
  • @Arvis http://stackoverflow.com/questions/3037637/c-sharp-what-if-a-static-method-is-called-from-multiple-threads might have some of the answers for assuaging your surprise. My best guess is that while it's static, each thread is manipulating what it BELIEVES to be the current snapshot and then syncs back to the main thread, though this is just a best guess. – David L Oct 08 '13 at 15:53
  • @Arvis my latest updates includes the thread-safe update to the dictionary, which outputs as you'd expect it to. – David L Oct 08 '13 at 16:34
  • @David L or just: `for (int i = 0; i < 50; i++) _dict[i] = "SECOND"` . Thanks for sharing the link. – Arvis Oct 09 '13 at 07:56