Understanding of ConcurrentDictionary in .NET

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 condition.

Every .NET developer knows that BCL (Base Class Library) has a key-value pairs collection whose name is Dictionary. But the problem is Dictionary is not thread-safe. Here is ConcurrentDictionary, which eliminates this problem, that is, provides thread safety and does the same things.

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

Race Condition Problem

I will explain the race condition problem with Dictionary.


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

The image above explains that we have multithread environment that consists of 2 threads. So, 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 mentioned above. Thread1 executes the first line and gets a false result. Then 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 key and value to the dictionary, too. But in our case, Thread2 will get an error, because the key, in 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:

сapacity: It is the initial size of the bucket which stores key/value pairs.

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:

Lock-free: The operations can proceed concurrently without the use of traditional locks.

Fine-grained: It divides data structure into smaller units and block them individually. It means, when you need to manipulate exact data that other thread can reach, blocking will happen in a unit level not an entire data structure.

Transanctional memory: (TM) 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:

Non-blocking functionalities: GetEnumerator, this[], TryGet, ContainsKey

Blocking items by individualy functionalities: TryAdd, TryUpdate, TryRemove

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 solve race condition problem (mentioned above) by ConcurrentDictionary.

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

public static void Main()
{
// Just for testing purpose
ThreadPool.QueueUserWorkItem((s) => ThreadSafeAddValue(15, $”Task1″));
ThreadPool.QueueUserWorkItem((s) => ThreadSafeAddValue(15, $”Task2″));
ThreadPool.QueueUserWorkItem((s) => ThreadSafeAddValue(15, $”Task3″));
ThreadPool.QueueUserWorkItem((s) => ThreadSafeAddValue(15, $”Task4″));
ThreadPool.QueueUserWorkItem((s) => ThreadSafeAddValue(15, $”Task5″));
ThreadPool.QueueUserWorkItem((s) => ThreadSafeAddValue(15, $”Task6″));
ThreadPool.QueueUserWorkItem((s) => ThreadSafeAddValue(15, $”Task7″));

Console.Read();
}

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

Benefits

As you can see the ConcurrentDictionary provides many benefits like:

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..).
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.
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.

Leave a Reply

Your email address will not be published. Required fields are marked *