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.
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.
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.
- private static void DoTryAdd(string key, int value)
- {
- Console.WriteLine("Is {0} Successfully added? {1}", key, phoneOrders.TryAdd(key, value));
- }
Now let’s call the method and see the output.
- Console.WriteLine("********************TryAdd********************");
-
- string key1 = "Prakash";
- int value1 = 5;
- DoTryAdd(key1, value1);
- string key2 = "Aradhana";
- int value2 = 7;
- DoTryAdd(key2, value2);
-
- string key3 = "DEF";
- int value3 = 6;
- DoTryAdd(key3, value3);
-
-
- DoTryAdd(key2, value2);
- PrintOrders();
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.
- private static void DoTryGetValue(string key)
- {
- int value;
- if (phoneOrders.TryGetValue(key, out value))
- {
- Console.WriteLine("The phone stock is with {0} is: {1}", key, value);
- }
- else
- {
- Console.WriteLine("Key {0} is invalid to fetch.", key);
- }
- }
Now let’s call the method and see the output.
- Console.WriteLine("\n********************TryGetValue********************");
-
-
- DoTryGetValue(key2);
- string key4 = "Satna";
- DoTryGetValue(key4);
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.
- private static void DoTryUpdate(string key, int newValue, int currentValue)
- {
- if (phoneOrders.TryUpdate(key, newValue, currentValue))
- {
- Console.WriteLine("Key {0} is successfully updated with new value {1}", key, newValue);
- }
- else
- {
- Console.WriteLine("Key {0} is failed to update with new value {1}", key, newValue);
- }
- }
Now let’s call the method and see the output.
- Console.WriteLine("\n********************TryUpdate********************");
-
- phoneOrders["Prakash"] = 7;
-
-
-
- DoTryUpdate(key2, value2 + 1, value2);
- DoTryUpdate(key4, 1, 1);
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.
- private static void DoTryRemove(string key)
- {
- int value;
- if (phoneOrders.TryRemove(key, out value))
- {
- Console.WriteLine("Key {0} and Value {1} pair removed", key, value);
- }
- else
- {
- Console.WriteLine("Key {0} is invalid to delete.", key);
- }
- }
Now let’s call the method and see the output.
- Console.WriteLine("\n********************TryRemove********************");
-
- Console.WriteLine("Elements count before removing: {0}", phoneOrders.Count);
- DoTryRemove(key3);
- DoTryRemove(key4);
- Console.WriteLine("Elements count after removing: {0}", phoneOrders.Count);
- PrintOrders();
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.
- private static void DoAddOrUpdate(string key, int newValue)
- {
-
- int newValue2 = phoneOrders.AddOrUpdate(key, 1, (key2, currentValue2) => newValue);
- Console.WriteLine("Key {0} is successfully added/updated with new value {1}", key, newValue2);
- }
Now let’s call the method and see the output.
- Console.WriteLine("\n********************AddOrUpdate********************");
-
- DoAddOrUpdate(key2, value2 + 2);
- DoAddOrUpdate(key4, 1);
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.
- private static void DoGetOrAdd(string key)
- {
-
- int newValue2 = phoneOrders.GetOrAdd(key, 1);
- Console.WriteLine("Key {0} is successfully retrieved/added with key {1}", key, newValue2);
- }
Now let’s call the method and see the output.
- Console.WriteLine("\n********************GetOrAdd********************");
-
- DoGetOrAdd(key2);
- string key5 = "Rewa";
- DoGetOrAdd(key5);
- PrintOrders();
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.