Working with Concurrent Collections in .NET Framework 4.0

Concurrent collections in .NET Framework 4.0 allow the developers to create type safe as well as thread safe collections. These collection classes form an essential part of the parallel programming feature and are available under the namespace System.Collections.Concurrent. Below are the different types of concurrent collections.

1. ConcurrentDictionary<TKey, TValue>
2. ConcurrentStack<T>
3. ConcurrentQueue<T>
4. ConcurrentBag<T>
5. BlockingCollection<T>

The producer and consumer pattern can easily be implemented while using ConcurrentStack, ConcurrentQueue and ConcurrentBag as they implement the interface IProducerConsumerCollection.

Need for Concurrent Collections

Though System.Collections namespace offers a wide range of collections, the only thing which limits our use of them in a multi-threaded or parallel environment is that they are not thread-safe. A non thread-safe collection will lead to race conditions and throw unexpected exceptions. In order to make them thread safe, a locking mechanism should be implemented as shown in the example below.

Dictionary<string, string> _dictionaryCollection = new Dictionary<string, string>();
object _lockingObject = new object();
public void AddToCollection(string key, string value)
{
            lock (_lockingObject)
            {
                _dictionaryCollection.Add(key, value);
            }
}
 

The above code will work fine in a multi-threaded operation but it is a little costly in terms of performance, since it blocks all the other threads until the current thread exits the lock. This is why concurrent collections are required. Concurrent collections can be shared across multiple threads with no explicit locks and it also increases the scalability and performance of multi-threaded operations.

ConcurrentDictionary

ConcurrentDictionary is used to create a thread-safe key value pair. It offers several methods, such as TryAdd, TryGetValue, AddOrUpdate, etc. All these methods are thread-safe. Below is the sample code for using ConcurrentDictionary.

class Program
{
 
    static void Main(string[] args)
    {
        //Consider this as a list from database which is huge and we need to represent this data as a keyvalue pair using ConcurrentDictionary
        string[] techWebSites = new string[] { "www.codeguru.com", "www.internet.com", "www.devx.com", "www.developer.com" };
 
        Task[] taskList = new Task[2];
 
        ConcurrentDictionary<int, string> dictionaryWebsites = new ConcurrentDictionary<int, string>();
 
        //With the creation of two tasks we have divided the load of populating the collection byt without doing any explicit synchronization
        taskList[0] = Task.Factory.StartNew(() =>
            {
                for (int i = 0; i < techWebSites.Length; i++)
                {
                    if (dictionaryWebsites.TryAdd(i, techWebSites[i]))
                        Console.WriteLine("Website {0} added to the dictionary!", techWebSites[0]);
                }
            });
 
        taskList[1] = Task.Factory.StartNew(() =>
        {
            for (int i = 0; i < techWebSites.Length; i++)
            {
                if (dictionaryWebsites.TryAdd(i, techWebSites[i]))
                    Console.WriteLine("Website {0} added to the dictionary!", techWebSites[0]);
            }
        });
 
        Task.WaitAll();
 
    }
}

ConcurrentQueue

ConcurrentQueue is a thread-safe first in first out (FIFO) collection. The code below is using ConcurrentQueue in a multi-threaded operation.

class Program
{
    static void Main(string[] args)
    {
        ConcurrentQueue<string> nameCollection = new ConcurrentQueue<string>();
        nameCollection.Enqueue("Adam Hollioke");
        nameCollection.Enqueue("Ben Hollioke");
        nameCollection.Enqueue("Ronie Irani");
        nameCollection.Enqueue("Patrick");
        nameCollection.Enqueue("Stuart Clarke");
        nameCollection.Enqueue("Mike Gatting");
 
        Task[] taskList = new Task[2];
 
        taskList[0] = Task.Factory.StartNew(() =>
            {
                string name = String.Empty;
                while (nameCollection.TryDequeue(out name))
                    Console.WriteLine("Name {0} is dequeued!", name);
            });
 
        taskList[1] = Task.Factory.StartNew(() =>
        {
            string name = String.Empty;
            while (nameCollection.TryDequeue(out name))
                Console.WriteLine("Name {0} is dequeued!", name);
        });
 
        Task.WaitAll();
        Console.ReadLine();
    }
}

ConcurrentStack

ConcurrentStack is a thread-safe collection to perform last in first out (LIFO) operations. The code below demonstrates the use of ConcurrentStack.

class Program
{
    static void Main(string[] args)
    {
        ConcurrentStack<int> numberCollection = new ConcurrentStack<int>();
        for (int i = 10; i <= 20; i++)
        {
            numberCollection.Push(i);
        }
 
        Parallel.For(0, 10, i =>
            {
                int value = 0;
                if (numberCollection.TryPop(out value))
                    Console.WriteLine("Value {0} is popped out!", value);
            });
 
        Console.ReadLine();
    }
}

A range of values can be pushed into or popped out from the ConcurrentStack using the methods PushRange and TryPopRange.

ConcurrentBag

ConcurrentBag is used to maintain the list of unordered items. TryTake and TryPeek methods can be used it take the item out of the bag and to peek for the value in the bag respectively. Below is the sample code to use ConcurrentBag.

class Program
{
    static ConcurrentBag<string> _entityCollection = new ConcurrentBag<string>();
    static void Main(string[] args)
    {
        _entityCollection.Add("Company1");
        _entityCollection.Add("Catalog1");
        _entityCollection.Add("Order1");
        _entityCollection.Add("Company2");
        _entityCollection.Add("Catalog2");
        _entityCollection.Add("Order2");
        _entityCollection.Add("Company3");
        _entityCollection.Add("Catalog3");
        _entityCollection.Add("Order3");
 
        ThreadStart threadStart = new ThreadStart(PrintEntities);
        Thread thread1 = new Thread(threadStart);
        thread1.Start();
        Thread thread2 = new Thread(threadStart);
        thread2.Start();
 
        Console.ReadLine();
    }
 
    static void PrintEntities()
    {
        string entity = String.Empty;
        while (_entityCollection.TryPeek(out entity))
        {
            if (_entityCollection.TryTake(out entity))
                Console.WriteLine(entity);
        }
    }
}

BlockingCollection

BlockingCollection is the perfect candidate for implementing the producer and consumer pattern over a thread-safe collection. The producer and consumer pattern generally spawns two threads where one thread adds the data to the collection and at the same time the collection would be consumed by the other. BlockingCollection takes care of blocking the threads until a vacant space is available in the collection while adding and until a data is added to the collection while consuming. I will cover the BlockingCollection in more detail in a future article.

I hope this article provided enough insight about concurrent collections in .NET Framework 4.0. Happy Reading!



About the Author

V.N.S Arun

I work for an MNC in Bangalore, India. I am fond of writing articles, posting answers in forums and submitting tips in dotnet. To contact me please feel free to make use of the "Send Email" option next to the display name.