Using a thread-safe dictionary in .NET C# Part 2: CRUD operations
July 29, 2015 Leave a comment
In the previous post we briefly introduced the ConcurrentDictionary object. We said that it was the thread-safe counterpart of the standard Dictionary object. The Dictionary object is not suited as a shared resource in multi-threaded scenarios as you can never be sure if another thread has added to or removed an element from the dictionary just milliseconds earlier. A ConcurrentDictionary is a good option to cure the shortcomings of the thread-sensitive Dictionary object but it is also more difficult to use.
We’ll briefly look at the 4 Try methods that enable you to insert, remove, update and lookup elements in the ConcurrentDictionary.
The 4 Try methods are the following:
- TryAdd: the same as the standard Add method of Dictionary but returns false if the element couldn’t be added instead of throwing an exception. The reason for a false outcome is usually that another thread has already added the element with that key
- TryRemove: the same as the standard Remove method of Dictionary but returns false if the element couldn’t be removed instead of throwing an exception. The reason for a false outcome is usually that another thread has already removed the element with that key
- TryGet: tries to get an element from the collection by its key and returns false if it wasn’t found. If the element is present then it is returned as an “out” parameter
- TryUpdate: this is the most difficult case of these 4. It updates the element to a new value but only if the provided comparison parameter matches the existing value
The following example demonstrates these methods:
ConcurrentDictionary<string, int> movieCategoriesOnStock = new ConcurrentDictionary<string, int>(); bool romanceAdded = movieCategoriesOnStock.TryAdd("Romance", 12); bool actionAdded = movieCategoriesOnStock.TryAdd("Action", 9); bool horrorAdded = movieCategoriesOnStock.TryAdd("Horror", 10); Debug.WriteLine("Horror added on first try: {0}", horrorAdded); bool horrorAddedAgain = movieCategoriesOnStock.TryAdd("Horror", 8); Debug.WriteLine("Horror added on second try: {0}", horrorAddedAgain); int horror; bool horrorFound = movieCategoriesOnStock.TryGetValue("Horror", out horror); Debug.WriteLine("Horror found: {0}", horrorFound); int comedy; bool comedyFound = movieCategoriesOnStock.TryGetValue("Comedy", out comedy); Debug.WriteLine("Comedy found: {0}", comedyFound); int romance; bool romanceRemoved = movieCategoriesOnStock.TryRemove("Romance", out romance); Debug.WriteLine("Romance removed on first try: {0}", romanceRemoved); bool romanceRemovedAgain = movieCategoriesOnStock.TryRemove("Romance", out romance); Debug.WriteLine("Romance removed on second try: {0}", romanceRemovedAgain); bool actionUpdated = movieCategoriesOnStock.TryUpdate("Action", 15, 9); Debug.WriteLine("Action updated on first try: {0}", actionUpdated); bool actionUpdatedAgain = movieCategoriesOnStock.TryUpdate("Action", 15, 9); Debug.WriteLine("Action updated on second try: {0}", actionUpdatedAgain); foreach (var kvp in movieCategoriesOnStock) { Debug.WriteLine(string.Format("{0}: {1}", kvp.Key, kvp.Value)); }
…which produces the following output:
Horror added on first try: True
Horror added on second try: False
Horror found: True
Comedy found: False
Romance removed on first try: True
Romance removed on second try: False
Action updated on first try: True
Action updated on second try: False
Action: 15
Horror: 10
Warning: beware of the TryUpdate method. It doesn’t throw an exception and is thread-safe but it will only update the value if you know in advance what the existing element has as a value. How can you find that? You can use TryGet but another thread may modify the value just in that very moment and the TryUpdate will fail because you pass in the incorrect value. For a better solution we’ll need to look at another method called AddOrUpdate. We’ll do that next.
View the list of posts on the Task Parallel Library here.