Are ConcurrentDictionary’s Keys and Values properties thread-safe?

While I do like the documentation, I tend to verify things with a small program when in doubt or I feel that I might be assuming too much.

The following code verifies that indeed you can enumerate the values collection safely while adding or removing keys from a separate thread to that on which the enumeration is taking place. This will not cause the usual collection was modified exceptions. In more detail, here are a couple of test cases

Case 1: Enumerating Values and deleting a key

If you follow the following sequence:

  • start enumerating the values collection from a thread
  • remove a key from a different thread that we have not enumerated yet
  • Continue enumerating on the original thread

The observed behavior is that the removed key will indeed be enumerated since it existed in the values collection when we started the enumeration. No exception will be raised.

Case 2: Enumerating Values and adding a key

  • start enumerating the values collection from a thread
  • add a new key from a different thread that we have not enumerated yet
  • Continue enumerating on the original thread

The observed behavior is that the added key will not be enumerated since it did not exist in values collection when we started to enumerate it. No exception will be raised whether we use TryAdd or add by assigning directly to the dictionary ie dictionary[key] = value.

Sample Code

Here is the sample program that demonstrates both cases:

ConcurrentDictionary<int, int> dictionary = new ConcurrentDictionary<int, int>();

// Seed the dictionary with some arbitrary values; 
for (int i = 0; i < 30; i++)
{
    dictionary.TryAdd(i, i);
}

// Reader thread - Enumerate the Values collection
Task.Factory.StartNew(
        () =>
        {
            foreach (var item in dictionary.Values)
            {
                Console.WriteLine("Item {0}: count: {1}", item, dictionary.Count);
                Thread.Sleep(20);
            }

        }
);

// writer thread - Modify dictionary by adding new items and removing existing ones from the end
Task.Factory.StartNew(
        () =>
        {
            for (int i = 29; i >= 0; i--)
            {
                Thread.Sleep(10);
                //Remove an existing entry 
                int removedValue;
                if (dictionary.TryRemove(i, out removedValue))
                    Console.WriteLine("Removed item {0}", removedValue);
                else
                    Console.WriteLine("Did not remove item {0}", i);

                int iVal = 50 + i*2;
                dictionary[iVal] = iVal;
                Thread.Sleep(10);
                iVal++;
                dictionary.TryAdd(iVal, iVal);
            }
        }
);

Console.ReadKey();

And here is the output in release mode:

Console output

Leave a Comment