Understanding of ConcurrentDictionary in .NET

Everything is becoming more concurrent, so usually server-based(multithreaded environments) applications are faced with non-thread-safe problems such as race conditions.

Especially when we are using the collections every .NET developer knows that BCL (Base Class Library) has a key-value pairs collection whose name is Dictionary. However, the problem is Dictionary is not thread-safe. So, it means we can face non-thread-safe problems. To eliminate these problems, we are using the ConcurrentDictionary, which provides thread safety and does the same things (in general).

Let’s look at one of the most common causes of problems!

Race Condition Problem

I will explain the race condition problem with the Dictionary.

So, let’s think that we have a method that checks the argument (key) with the Dictionary ContainsKey method. And if the result is false, then it adds that key and value (arguments) to the dictionary instance. Otherwise the code block will do nothing.

Race condition problem

The above explanation is illustrated in the image and also shows the runtime steps of 2 threads, too. So, in the image we have multithread environment that consists of 2 threads. And it means two different threads are reading same code block at the same time (at the same time is an illusion, in fact they do not work at the same time but this is a topic for another article).

Let’s look at the image step by step. ‘Thread1’ executes the first line and gets a false result. Then, the context switches to ‘Thread2’. It executes the first line and gets false. So, ‘Thread1’ and ‘Thread2’ enter the if block. Here is the problem, when the context switches to ‘Thread1’, it will add a key and value to the dictionary. After that, the context switches to ‘Thread2’, which will try to add a key and value to the dictionary, too. But in our case, ‘Thread2’ will get an error because the key in the dictionary should be unique.

This is what we call the Race Condition problem because, in multithread environments, the threads are always racing between each other.

There are different types of race condition problems; this example is one of them.

ConcurrentDictionary

ConcurrentDictionary is the generic implementation of a hash-table data structure that is used to store key/value pairs. And also it is providing thread-safety for the using in multithreading scenarios.

For constructing the ConcurrentDictionary, it has 2 items:

  1. сapacity: It is the initial size of the bucket which stores key/value pairs.
  2. concurrencyLevel: It defines the count of threads that will work on the given data structure at the same time. For example, if you have specified concurrentLevel = 2, then the rest of the threads will wait for these 2 threads to finish (a least one of them must be released). So, it helps us to configure the estimated number of threads that will update the items (key/value pairs) concurrently.

In general, implementations of concurrent data structures have 3 techniques:

  1. Lock-free: The operations can proceed concurrently without the use of traditional locks.
  2. Fine-grained: It divides data structures into smaller units and blocks them individually. It means that you need to manipulate exact data that other threads can reach, blocking will happen in a unit level not an entire data structure.
  3. Transactional memory: abstracts concurrent operations into transactions, which are executed atomically and isolated from each other.

The ConcurrentDictionary uses a fine-grained technique for getting better performance in the case of a multithread environment.

Methods

We can group the methods in terms of implementation:

  1. Non-blocking functionalities: GetEnumerator, this[], TryGet, ContainsKey
  2. Blocking items by individually functionalities: TryAdd, TryUpdate, TryRemove
  3. Blocking all dictionary items (non-effective one): Count, IsEmpty, Keys, Values, CopyTo, Clear, ToArray

Let’s continue by writing an example. The basic code below has solved the race condition problem (mentioned above) by ConcurrentDictionary.

public class Program
{
    public static ConcurrentDictionary<int, string> dictionaryInstance =
                          new ConcurrentDictionary<int, string>();

    public static void Main()
    {
        for (int i = 0; i < 10; i++)
        {
            string valueStr = $"Task{i + 1}";
            ThreadPool.QueueUserWorkItem((s) => ThreadSafeAddValue(15, valueStr)); // Just for testing purpose
        }

        Console.Read();
    }

    static void ThreadSafeAddValue(int key, string value)
    {
        dictionaryInstance.TryAdd(key, value);
    }
}

Benefits

As you can see, the ConcurrentDictionary provides many benefits like

  1. Thread safety with optimization because it increases the performance of operations. For that reason, it is better than manual/customized locking/critical section technique (Monitor, lock, etc..).
  2. It uses lightweight synchronization (SpinWait, SpinLock) that uses spinning before putting threads to wait; for short wait periods, spinning is less expensive than waiting, which involves kernel switching.
  3. It gives already tested key/value pairs functionalities which ready to be used (for all lazy developers 😊).

As you can see the ConcurrentDictionary would be the best choice instead of using Dictionary in case of a multithread environment.


Similar Articles