Concurrent Collections in .NET: ConcurrentDictionary - Part One

Today I am here to talk about Concurrent Collections in .NET. First we will discuss Concurrent Collections in general then go through ConcurrentDictionary in detail and cover what, when and how to use it.
 
To begin with, let’s put out some questions to understand the concept related to Concurrent Collections.
 
concept
 
What are the Concurrent collections and when to use them?
 
Concurrent collections (Namespace: System.Collections.Concurrent) are basically thread safe collections and are designed to be used in multithreading environment. They also provide type safety due to the generic implementation.
 
These collections should be used when they are getting changed or data is added/updated/deleted by multiple threads. If the requirement is only read in multithreaded environment then generic collections can be used.
 
If locking is needed at a few places, manual locking or synchronization techniques can also be used however if it is required at several places, using concurrent collection is a good choice.
 
Concurrent collections are designed to be used in cases when excessive thread safety is required, overly using manual locking can lead to deadlock and other issues.
 
Under the hood, concurrent collections use several algorithms to minimize the thread blocking.
 
What are the commonly used concurrent collections we have? Does concurrent collections addresses race conditions?
 
There are several types of concurrent collections but commonly used are as follows.
  • ConcurrentDictionary<TKey, TValue> -> Thread safe version of Dictionary
  • ConcurrentBag<T> -> New thread safe unordered collection
  • ConcurrentQueue<T> -> Thread safe version of generic queue (FIFO structure)
  • ConcurrentStack<T> -> Thread safe version of generic stack (LIFO structure)
Out of the above list, ConcurrentDictionary can be used as a general purpose collection whereas others are mostly used in producer-consumer (i.e. dedicated threads for add-delete) scenarios.
 
Concurrent collections address the race condition and thread safety inside their implementation (e.g. inside AddOrUpdate method of ConcurrentDictionary) however it doesn’t guarantee the race conditions among threads between custom method calls.
 
Hope at this point, you understand what concurrent collections are and when to use them.
 
Now let’s see how to use them.
 
We will begin with concurrent dictionary as it’s the most commonly used.
 
dictionary
 
As mentioned earlier, Concurrent Dictionary is the general purpose collection and can be used in most of the cases. It has exposed several methods and properties and commonly used methods are as follows.
  • TryAdd
     
    TryAdd returns true if it’s successfully added  else the key/value pair  returns false primarily due to duplicate key.
     
    Let’s have a look at the code below to understand how to use it.
    1. private static void DoTryAdd(string key, int value)  
    2.         {  
    3.             Console.WriteLine("Is {0} Successfully added? {1}", key, phoneOrders.TryAdd(key, value));  
    4.         }  
    Now let’s call the method and see the output.
    1. Console.WriteLine("********************TryAdd********************");  
    2. //TryAdd returns true, if it successfully added.  
    3. string key1 = "Prakash";  
    4. int value1 = 5;  
    5. DoTryAdd(key1, value1);  
    6. string key2 = "Aradhana";  
    7. int value2 = 7;  
    8. DoTryAdd(key2, value2);  
    9.   
    10. string key3 = "DEF";  
    11. int value3 = 6;   
    12. DoTryAdd(key3, value3);  
    13.   
    14. ////TryAdd returns false, if it failed to add due to duplicate key.   
    15. DoTryAdd(key2, value2);  
    16. PrintOrders();  
    Output
     
    Output
     
  • TryGetValue (Also present in generic dictionary)
     
    If key is present, TryGetValue fetches the value and returns true and if key isn’t present, it simply returns false without throwing any exception. Let’s have a look at the code below to understand how to use it.
    1. private static void DoTryGetValue(string key)  
    2.         {  
    3.             int value;             
    4.             if (phoneOrders.TryGetValue(key, out value))  
    5.             {  
    6.                 Console.WriteLine("The phone stock is with {0} is: {1}", key, value);  
    7.             }  
    8.             else  
    9.             {  
    10.                 Console.WriteLine("Key {0} is invalid to fetch.", key);  
    11.             }  
    12.         }  
    Now let’s call the method and see the output.
    1. Console.WriteLine("\n********************TryGetValue********************");  
    2. //TryGetValue returns true and fetches the value, if key exist else returns false   
    3. //but doesn't throw exception in case of key not present.   
    4. DoTryGetValue(key2);  
    5. string key4 = "Satna";  
    6. DoTryGetValue(key4);  
    Output
     
    Output
     
  • TryUpdate
     
    TryUpdate returns true and updates the value if key exists and passes current value if the third parameter matches with the one in collection, else it returns false. Let’s have a look at thr code below to understand how to use it.
    1. private static void DoTryUpdate(string key, int newValue, int currentValue)  
    2.         {  
    3.             if (phoneOrders.TryUpdate(key, newValue, currentValue))  
    4.             {  
    5.                 Console.WriteLine("Key {0} is successfully updated with new value {1}", key, newValue);  
    6.             }  
    7.             else  
    8.             {  
    9.                 Console.WriteLine("Key {0} is failed to update with new value {1}", key, newValue);  
    10.             }  
    11.         }  
    Now let’s call the method and see the output.
    1. Console.WriteLine("\n********************TryUpdate********************");  
    2. //Traditional or single threaded way to update the dictionary.  
    3. phoneOrders["Prakash"] = 7;  
    4.   
    5. //Multi-threaded way to update.   
    6. //If key exists and current value matches, new value is updated.  
    7. DoTryUpdate(key2, value2 + 1, value2);  
    8. DoTryUpdate(key4, 1, 1);  
    Output
     
    Output
     
  • TryRemove
     
    If key is present, TryRemove deletes the element and returns true else it simply returns false without throwing any exception. Let’s have a look at the code below to understand how to use it.
    1. private static void DoTryRemove(string key)  
    2.         {  
    3.             int value;  
    4.             if (phoneOrders.TryRemove(key, out value))  
    5.             {  
    6.                 Console.WriteLine("Key {0} and Value {1} pair removed", key, value);  
    7.             }  
    8.             else  
    9.             {  
    10.                 Console.WriteLine("Key {0} is invalid to delete.", key);  
    11.             }  
    12.         }  
    Now let’s call the method and see the output.
    1. Console.WriteLine("\n********************TryRemove********************");  
    2. //Try Remove   
    3. Console.WriteLine("Elements count before removing: {0}", phoneOrders.Count);   
    4. DoTryRemove(key3);  
    5. DoTryRemove(key4);  
    6. Console.WriteLine("Elements count after removing: {0}", phoneOrders.Count);  
    7. PrintOrders();  
    Output
     
    Output
     
  • AddOrUpdate
     
    If key is present, AddOrUpdate updates the existing value with a new one and returns the new value else it adds the key with the value passed in the second parameter and returns the same. It is designed not to fail irrespective if key is present or not as it handles both the possible cases. Let’s have a look at the code below to understand how to use it.
    1. private static void DoAddOrUpdate(string key, int newValue)  
    2.         {  
    3.             //2nd param is the value to be added for an absent key.  
    4.             int newValue2 = phoneOrders.AddOrUpdate(key, 1, (key2, currentValue2) => newValue);              
    5.             Console.WriteLine("Key {0} is successfully added/updated with new value {1}", key, newValue2);                          
    6.         }  
    Now let’s call the method and see the output.
    1. Console.WriteLine("\n********************AddOrUpdate********************");  
    2. //If key exists, new value is updated else key is added with value passed in 2nd param.  
    3. DoAddOrUpdate(key2, value2 + 2);  
    4. DoAddOrUpdate(key4, 1);  
    Output
     
    Output
     
  • GetOrAdd
     
    If key is present, GetOrAdd fetches the value else it adds the key with the value passed in the second parameter and returns the same. Similar to AddOrUpdate,iIt is also designed not to fail irrespective if key is present or not as it handles both the possible cases. Let’s have a look at code below to understand how to use it.
    1. private static void DoGetOrAdd(string key)  
    2.         {  
    3.             //2nd param is the value to be added for an absent key.  
    4.             int newValue2 = phoneOrders.GetOrAdd(key, 1);  
    5.             Console.WriteLine("Key {0} is successfully retrieved/added with key {1}", key, newValue2);  
    6.         }  
    Now let’s call the method and see the output.
    1. Console.WriteLine("\n********************GetOrAdd********************");  
    2.             //If key exists, corresponding value is returned else key is added with value passed in 2nd param.  
    3.             DoGetOrAdd(key2);  
    4.             string key5 = "Rewa";  
    5.             DoGetOrAdd(key5);  
    6.             PrintOrders();  
    Output
     
    Output
     
As you can see in the above output Key ‘Rewa’ is added with value 1 using GetOrAdd method as the key was not present in the collection.

Conclusion

 
In this article we have gone through what concurrent collections are and when to use them. We have discussed ConcurrentDictionary in detail including its common methods and illustrated the code and output. In the next part of the Concurrent Collection series, will talk about another collection and its uses.
 
You can also download the attached demo project (ConcurrentDictionaryDemo.zip) to go through the full source code used in the article.
 
Hope you have liked the article. Look forward to your comments/suggestions.