Click here to Skip to main content
15,860,844 members
Articles / Programming Languages / C# 4.0

Thread-safe Collections in .NET 4.0

Rate me:
Please Sign up or sign in to vote.
4.75/5 (10 votes)
16 Apr 2011CPOL8 min read 71.8K   824   46   10
This article gives the reader a fair understanding of the .NET 4.0 concurrent collections.

Introduction

I was playing around with .NET 4.0 and one of the very interesting features that I felt good to share is concurrent collections. .NET 4.0 now has a new namespace System.Collections.Concurrent. Earlier, .NET had collections to represent data in memory but these collections were not type safe; a collection when defined can have any possible type of object in it, which meant we had to explicitly type cast back to the original type before performing any operation. .NET 2.0 introduced Generics in collections and type safety was ensured. Supplying the proper type is ensured at compile time only, it will not wait until run time blow up. It also has one glitch, it is type safe but not thread safe - when two or three threads simultaneously access a collection, then we need to explicitly lock the collection so that only one thread can be entered at a particular point of time for data modification.

After the introduction of the new System.Collections.Concurrent namespace, developers no longer have to worry about locking and unlocking objects. Concurrent collections are meant for thread safety - all the locking mechanism is handled inside the collections. The classes in this namespace is altogether a new write up of collections - they arrange data inside the collection as a collection of segments, where each segment is an array of items.

Using the code

Here is the set of collection classes available as part of the Concurrent collections.

  • BlockingCollections <t>
  • ConcurrentBag <t>
  • ConcurrentDictionary < tkey,t >
  • ConcurrentQueue <t>
  • ConcurrentStack <t>
  • OrderablePartitioner <t>
  • Partitioner
  • Partitioner <t>

As this is a big list, I am splitting up the explanataion into more than one article. The first one I will explain is BlockingCollections<T>. The sample code uses Visual Studio 2010 Express edition; it is recommended to use it or higher versions.

BlockingCollections<T>

Provides bounding and blocking capabilities for thread safe collections which implement IProducerConsumerCollection<T>. The code shown below demonstrates the use of BlockingCollection<T>. While reading an item, the BlockingCollection will block the thread until an item is available in the collection, like a consumer waiting for the producer to produce. When an item is available in the collection, it can consume the item from the collection.

Here the Publisher/Consumer model is demonstrated; ServedItem holds the details of the item served.

We have two tasks that both run asynchronously on shared data (servedList) but in a thread safe manner. Just to have a feel of the Publisher/Consumer style, we have a while loop running continuously in the ConsumingTask asynchronous process to consume the items published. servingTask will ask for order and serves (publishes) the order, which is consumed immediately by ConsumingTask. Run the console application to see the results. If we don't want anymore orders, enter 'N' and then close the console window manually. Here we have a thread safe, Publisher Consumer pattern free of cost with .NET 4.0.

C#
//
public class BlockingCollectionSample
{
    static void Main(string[] args)
    {
        BlockingCollection<serveditem> servedList = 
                   new BlockingCollection<serveditem>();
        //obj.Take();
        Task ConsumingTask = Task.Factory.StartNew(() => 
                               { while (true) { Consumed(servedList); } });
        //Task T1 = Task.Factory.StartNew(() => { obj.Add(1);
        Console.WriteLine("Published :" + 1); Thread.Sleep(2000);
        obj.Add(2); Console.WriteLine("Published :" + 2);
        Thread.Sleep(2000); obj.Add(3);
        Console.WriteLine("Published :" + 3);
        Thread.Sleep(2000); });
        Task servingTask = Task.Factory.StartNew(() =>
        {
            string wouldYouLikeToOrderMore = string.Empty;
            do
            {
                Console.WriteLine("What would you like to have Sir");
                ServedItem item1 = new ServedItem();
                item1.Name = Console.ReadLine();

                published(servedList, item1);
                Console.WriteLine("Would you Like to Order More (Y/N)");
                wouldYouLikeToOrderMore = Console.ReadLine();
            } while (wouldYouLikeToOrderMore.ToLowerInvariant().Equals("y"));
        });
       
        Task.WaitAll(servingTask,ConsumingTask );

    }
    public static void published(BlockingCollection<serveditem> Items,ServedItem item )
    {
        Items.Add(item);
        Console.WriteLine("Served Item = {0}",item.Name );
    }
    public static void Consumed(BlockingCollection<serveditem> Items)
    {
        Console.WriteLine("Consumed Item = {0}", Items.Take().Name );
    }
}

public class ServedItem 
{
    public string Name { get; set; }
    public float Price { get; set; }
    
}

BlockingCollection<T> provides a method addCompleted() which marks a collection as complete for adding, no more additions can be made to the BlockingCollection after this. This can be verified using the property isAddingCompleted. This is how a thread can ensure reading only after the completion of writing.

Features
  • Provides blocking and bounding capabilities for thread safe collections which implement IProducerConsumerCollections.
  • Ideal for Producer Consumer model, it can be used as a buffer between the Producer and Consumer.
  • Blocks an attempt to remove from an empty collection, also blocks an attempt to add an item from a bound collection.
  • Provides GetConsumingEnumerable() which is useful for consumers to enumerate a collection by calling the Take() method. MoveNext() on the enumerator will block the attempt if the collection is empty.

ConcurrentBag < T >

Another type in the new namespace is ConcurrentBag, which is a thread safe unordered collection of objects. Elements will be stored in segments internally; each segment will hold an array of items, and each segment start and end index with the segment number is used for tracking. Initially, the index will start at the first segment first element; as elements are removed from the bag, the index will move forward to the next available element.

To demonstrate ConcurrentBag, in the provided code, see ConcurrencyBagSample.cs. Shown below is the sample code:

C#
public class Starter
{
    public static void Main()
    {
        ConcurrencyBagSample concurrencyBagSample = new ConcurrencyBagSample();
        concurrencyBagSample.DemoConcurrentBag(); 
    }
}
public class ConcurrencyBagSample
{
    delegate void myDelegate(string strParam);
    public void DemoConcurrentBag()
    {
        ConcurrentBag<int> concurrentBag = new ConcurrentBag<int>();

        //Single thread fills the data
        for (int i = 1; i <= 10; i++)
        {
            concurrentBag.Add(i);
        }
        
        Action  runMethod = () =>
        {
            while (true)
            {

                int result; if(concurrentBag.TryTake(out result))
                    Console.WriteLine("This isThread = {0} accessing " + 
                      "concurrentbag value = {1} ",
                      Thread.CurrentThread.Name ,result);
            }
        };
            
        Thread thread1 = new Thread(new ThreadStart(runMethod));
        thread1.Name = "Thread1";
        thread1.Start();

        Thread thread2 = new Thread(new ThreadStart(runMethod));
        thread2.Name = "Thread2";
        thread2.Start();

        Thread thread3 = new Thread(new ThreadStart(runMethod));
        thread3.Name = "Thread3";
        thread3.Start();

        Thread.CurrentThread.Join();  
        

    }
}

In the above sample, MainThread feeds ConcurrentBag with 10 elements; later 3 threads access the ConcurrentBag in a thread safe manner. Data is shared across all the threads but in a thread safe manner; run the program to see the output. ConcurrentBag is an excellent abstraction to the underlying details of old locking complexities.

Features
  • ConcurrentBag<t> is a new type of data structure, it is a thread safe unordered collection of objects. Items can be added to a ConcurrentBag in any order, and retrieved in any order. ConcrrentBag<t> can be used in situations where the order of items in the collection doesn't matter. For example, puzzle games where we need to pick any one number out of ten available numbers.
  • ConcurrentBag<t> is implemented using the System.Threading.ThreadLocal<t> type; each thread accessing ConcurrentBag<t> will have its own local list of items.
  • Very little synchronization overhead is needed as opposed to other data strcutures since each thread maintains its own items in the collection. Since ConcurrentBag provides the global view to all threads, if a thread doesn't find an element and another thread is holding it, then it will steal the item from that thread.
  • More efficient solution if we are implementing an unordered Publisher Consumer model.
  • IsEmpty, Count, ToArray(), GetEnumerator() lock the entire data structure; parallel calls to Add(), Take() may take significant amount of time.

ConcurrentDictionary < tkey,tvalue >

  • Thread safe implementation of a strongly typed dictionary. Represents a thread safe collection of key value pairs that can be accessed by multiple threads.
  • Takes the full advantage of available hardware like n-core processor; an ideal option when workload is heavy and thread safe collections are needed.
  • If there is a need to update the data in a dictionary, then concurrentDictionary<tkey,> is a suitable option; if the dictionary is for read-only purposes, then the normal Dictionary gives better performance.
  • If the number of read operations is more as opposed to update operations, then ConcurrentDictionary<tkey,> gives better performance than a locked dictionary. The decision to use which collection depends on the number and type of operations we perform on the collection.
  • It is worth noting the operation AddorUpdate(...) on the data dictionary; if the element already exists, it updates, else adds the element. Essentially, it replaces the methods TryAdd(), TryUpdate().

ConcurrentQueue <t>

  • ConcurrentQueue<t> is a data structure in .NET Framework 4.0 that allows thread safe access to first in first out (FIFO) ordered elements.
  • Internally, data is arranged as segments of arrays with lock free implementations, and each head and tail operation is implemented in a lock free manner, whereas a normal queue has a backend array and depends on the external use of monitors.
  • Performance studies on this data structure revealed that it is not an ideal option when the workload for a thread is low and there are a large number of threads needed. This is ideal for situations where the workload for a thread is heavy.
  • Ideal for Producer Consumer model, it can be used as a buffer between the Producer and Consumer.

ConcurrentStack <t>

  • ConcurrentStack<t> is an implementation of the classic LIFO that provides thread safe access to data.
  • This is applicable in situations where new data should be processed in preference to old data. Consider a parking lot, a car which comes in first is the one that can go last (as it has to wait for other cars to move on), or a CD stack where we can't remove the bottom one unless we remove the others from the top.
  • Ideal for Producer Consumer model, it can be used as a buffer between the Producer and Consumer.
  • Performance studies revealed that when the Producer Consumer model is applied to ConcurrentStack<t> vs. the plain stack structure with a proper synchronized mechanism applied, there is not much difference in performance observed with many threads and different work loads, but still it is recommended to use ConcurrentStack.
  • Push and Pop operations on stack for N elements has the complexity of o(N). ConcurentStack<t> provides PushRange, popRange operations: at a stretch we can insert multiple elements and pop multiple elements, which saves a lot of locks as against a normal stack. We can implement similar kind of functionality to a stack using synchronization techniques, but it is always recommended to use the ConcurrentStack<t> data structure.

Points of Interest

  1. Concurrent collections allow us to write thread safe code without actually worrying about the technical handling of synchronization.
  2. Performance effective way of handling thread safety as opposed to the old locking mechanism. Concurrent collections have the edge of performance over locking/unlocking objects.

Disclaimer

This article uses a sample which is not for direct production deployment. This is to give the reader an idea on how Concurrent Collections work, it is the reader's responsibility to follow the necessary best practices while implementing Concurrent Collections.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
India India
I have around 9 years of experience in Microsoft technologies, .Net 2.0,3.5, Asp.net MVC developed small to medium scale products using Silverlight 2.0, Asp.net Ajax technologie and Javascript frameworks.

Comments and Discussions

 
QuestionData structure to maintain uniqueness Pin
Kumar Tanmay8-Jan-15 21:47
Kumar Tanmay8-Jan-15 21:47 
QuestionVisual Basic Translation Pin
Xylen.C429-Sep-13 8:03
Xylen.C429-Sep-13 8:03 
GeneralMy vote of 2 Pin
sergey.rush30-Oct-11 9:33
sergey.rush30-Oct-11 9:33 
GeneralI used some of this in 5th part of TPL article I did a while ago Pin
Sacha Barber14-Apr-11 22:09
Sacha Barber14-Apr-11 22:09 
Questionerror typo Pin
johnatan1714-Apr-11 19:02
johnatan1714-Apr-11 19:02 
AnswerRe: error typo Pin
Ramu Sangabathula Original14-Apr-11 19:57
Ramu Sangabathula Original14-Apr-11 19:57 
GeneralReading the article Pin
Justin Helsley14-Apr-11 18:09
Justin Helsley14-Apr-11 18:09 
GeneralRe: Reading the article Pin
Ramu Sangabathula Original14-Apr-11 20:05
Ramu Sangabathula Original14-Apr-11 20:05 
GeneralRe: Reading the article Pin
JV999914-Apr-11 21:11
professionalJV999914-Apr-11 21:11 
AnswerRe: Reading the article Pin
Ramu Sangabathula Original15-Apr-11 3:28
Ramu Sangabathula Original15-Apr-11 3:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.