Chapter 6. Concurrent Collections

Concurrent Collections Me! Me! Me!

Thread safety has been a major concern for API (Application Programming Interface) or framework developers for many years. Achieving thread safety had been inherently hard. These developers have always dreamt of collections that are primitively thread-safe, not requiring any locking mechanism when consumed in a multi-threaded environment.

Moreover, applications that can do more than one thing (also known as multi-threaded applications) are almost taken for granted these days. If an application fails to live up to this expectation, then it has little chance of surviving.

Thanks to Concurrent Collections from Microsoft! These collections make writing thread-safe code a breeze. They encapsulate all locking mechanisms inside the generic data structures, so that the developers don't have to worry about it.

In this chapter, we shall learn how concurrent collections are making writing synchronized code easy. To learn about this power, in this chapter we shall create applications to:

  • Create bank queue simulation

  • Develop a survey engine simulation system

  • Devise a new generic data structure to find the most in-demand item at a store

Creating and running asynchronous tasks

This chapter deals with concurrency. We have to simulate that too. We will use the Task Parallelization Library (also known as TPL) to simulate several concurrent tasks that will deal with concurrent collections. TPL is available in .NET 4.0 for creating and running asynchronous tasks. Following are few of the patterns we'll use in this chapter. Task is a specialized class and it abstracts the creation of primitive threads.

With this API, writing asynchronous routines becomes more conceptual.

Pattern 1: Creating and starting a new asynchronous task

Task.Factory.StartNew(() => AddNewFruitNames());

Pattern 2: Creating a task and starting it off a little later

Task keepAdding = new Task(() => AddNewFruitNames());
//Some other code that has to be done before this task gets started.
keepAdding.Start();

Pattern 3: Waiting for all running tasks to complete

Task.WaitAll(task1, task2, task3, task4, task5, task6, task7, task8);

Pattern 4: Waiting for any particular task

Task.WaitAny(someTask);

Pattern 5: Starting a task with an initial parameter

Task voting1 = new Task((obj) => VoteThisOne(obj), "initial value");

Note

Warning! This is by no means a chapter on TPL. These are just the patterns we will use in this chapter.

Simulating a survey (which is, of course, simultaneous by nature)

Surveys could be very concurrent in nature. Assume that someone from every part of the world is taking part in a survey and their responses to a single survey question could come in at the same time.

In this example, we shall simulate such a situation using concurrent collection and TPL. Following are the assumptions about the simulation:

  • People can add more options at runtime for a given survey question

  • Many people can take the survey at the same time, which means it will be concurrent in nature

Time for action – creating the blocks

Follow the given steps:

  1. Create a console application. Call it SurveyDonkeyTest.

  2. Make sure you add the following directives:

    using System.Collections.Concurrent;
    using System.IO;
    using System.Threading.Tasks;//Needed for TPL
  3. Add the following variables to the file Program.cs:

    static ConcurrentDictionary<string, int> votes = new ConcurrentDictionary<string, int>();
    
    static ConcurrentBag<string> options = new ConcurrentBag<string>();
    
    static List<string> fruits = new List<string>();
  4. Add the following code to read all fruit names:

    //You can create your own version of Fruits.txt to store names of
    //few fruits
    //or you can download it from the book website
    StreamReader optionReader = new StreamReader("Fruits.txt");
    
    fruits.AddRange(optionReader.ReadToEnd().Split(new char[] { '
    ', '
    ' }, StringSplitOptions.RemoveEmptyEntries));
    
    optionReader.Close();
  5. Add a few initial options in Main():

    //Initial options
    options.Add("Apple");
    options.Add("Banana");
    options.Add("Orange");
    
    //Adding initial options to vote for
    votes.TryAdd("Apple", 0);
    votes.TryAdd("Banana", 0);
    votes.TryAdd("Orange", 0);
    //Start adding random fruits from the fruits.txt file as we
    //vote. This is similar to the situation where people started
    //buying and suddenly more options become available for them 
    //to choose from.
    keepAdding.Start();
  6. Add the following tasks, start them and wait on them:

    Task showResult = new Task(() => ShowVotingResult());Task keepAdding = new Task(() => AddNewFruitNames());
    //Creating tasks that will act as voters
    List<Task> voters = new List<Task>();
    
    for (int i = 0; i < 100; i++)
    {
      //Creating a new task with a randomly chosen object from the
      //options pool
      Task voter = new Task((obj) => VoteThisOne(obj), options.ElementAt((new Random()).Next(options.Count)));
      //Wait for some time, otherwise we might get the same random element.
      //This is done to make sure we get an evenly distributed voting :) Might not
      //happen in real world though. You can delete this line to see what happens.
      System.Threading.Thread.Sleep(5);
      //Adding this voter to the list of voters
      voters.Add(voter);
    }
    //Let's start the voting process 
    voters.ForEach(voter => voter.Start());
    
    //Wait till the voting is completed
    Task.WaitAll(voters.ToArray());
    
    //Show the result
    showResult.Start();
    
    //Wait for the results to be shown properly
    Task.WaitAny(showResult);
  7. Add the following method to add new options at runtime:

    ///<summary>
    ///Make 10 tries to add new fruit options to the voting.
    ///It is like having new nominations during the voting process :)
    ///</summary>
    private static void AddNewFruitNames()
    {
      for(int i=0 ;i<10; i++)
      {
        string newFruit = fruits[(new Random()).Next(fruits.Count)];
        if (!options.Contains(newFruit))
        {
          options.Add(newFruit);
          votes.TryAdd(newFruit, 0);
        }
      }
    }
  8. Add the following method to vote for an option:

    ///<summary>
    ///Voting process by individual voters.
    ///</summary>
    ///<param name="obj"> The option for which this voter is voting.///</param>
    private static void VoteThisOne(object obj)
    {
      string option = (string)obj;
      votes[option]++;
    }
  9. Add the following method to show voting results:

    private static void ShowVotingResult()
    {
      Console.WriteLine("Voting Result");
      Console.WriteLine("--------------");
      foreach (string fruitName in votes.Keys)
      {
        int thisManyPeople = 0;
        votes.TryGetValue(fruitName,out  thisManyPeople );
        Console.WriteLine(thisManyPeople.ToString() + "peoplelike " + key);
      }
    }
  10. Add the following line at the beginning of the Main() method:

    Console.WriteLine("Voting is in progress...");

We are now ready to execute our test. As you run the program, you will get the following output:

Time for action – creating the blocks

What just happened?

At the heart of any survey is a table, and essentially a dictionary, which tracks the number of votes each option has got. In the previous code, we have created 100 voting tasks that vote random options.

In the previous example, two concurrent collections are used. A ConcurrentBag collection is used to update the available voting options at runtime. ConcurrentBag is optimized for addition and deletion from the same thread. votes is a ConcurrentDictionary that holds the response of individual voter tasks. The TryAdd() method is used to add initial options to the dictionary. This method is the implementation of the IProducerConsumerCollection() interface method.

Try, in the beginning of this method, means that it might fail in a multi-threaded environment if primitive Add is used. Imagine a situation where you are trying to add a few values against a dictionary key, while it was deleted by some other thread.

ConcurrentDictionary, similar to the thread-unsafe dictionary, still provides support for key indexers. So, votes[option]++; will increase the voting count for the options mentioned.

ConcurrentBag is a collection that implements an IEnumerable<T> interface; so, it supports all the LINQ Standard Query Operators.

As votes is a concurrent collection, and it is being accessed by several threads at runtime, it might so happen that it runs out of elements when we want to get the associated value for a given key.

The votes.TryGetValue(fruitName, out thisManyPeople) method returns the value for the key fruitName in votes and stores it in the variable thisManyPeople, or in other words, it stores the number of votes that the fruit with the name fruitName has received from voting.

Devising a data structure for finding the most in-demand item

In this section, we will develop a generic concurrent, move-to-front list with the following functionalities:

  • It will allow the addition of new elements.

  • It will show the top element and how many times that element has been accessed.

  • It will let users take the top n elements sorted by demand. So, the item that is sought most should be available as the first item on the list.

Time for action – creating the concurrent move-to-front list

Follow the given steps:

  1. Create a new class library project. Call it TSList.

  2. Change the name of the class from Class1.cs to ConcurrentMTFList.cs.

  3. Change the class header as follows:

    public class ConcurrentMTFList<T> where T:IComparable
  4. Add the following variable and the constructor:

    ConcurrentDictionary<T,int> _innerDS;
    ///<summary>
    ///Initializes the internal data structure.
    ///</summary>
    public ConcurrentMTFList()
    {
      _innerDS = new ConcurrentDictionary<T, int>();
    }
  5. Add the following method to add new items to the list:

    ///<summary>
    ///Adds an item to the concurrent list.
    ///</summary>
    ///<param name="item">item to be added.</param>
    public void Add(T item)
    {
      _innerDS.AddOrUpdate(item, 0, (i, c) => _innerDS[i]=1);
    }
  6. Add the following method to check whether an item is present in the list or not:

    ///<summary>
    ///Checks whether an element is present in the collection.
    ///</summary>
    ///<param name="item">item to be sought</param>
    ///<returns>return true, </returns>
    public bool Contains(T item)
    {
      return _innerDS.ContainsKey(item);
    }
  7. Add the following method to perform the active search on the list:

    ///<summary>
    ///Active search of the collection.
    ///If the item found, it moves it to the top of the collection.
    ///</summary>
    ///<param name="item"> Item to search in the collection. </param>
    ///<returns> True if the search is found else returns False ///</returns>
    public bool Search(T item)
    {
      if (Contains(item))
      {
        _innerDS[item]++;
        return true;
      }
      else
      {
        _innerDS.GetOrAdd(item, 0);
        return false;
      }
    }
  8. Add the following property to find the top-most sought-after element in the list:

    ///<summary>
    ///Find the most sought after item.
    ///</summary>
    public T Top
    {
      get
      {
        try
        {
          return _innerDS.Where(c => c.Value == _innerDS.Values.Max()).Select(c => c.Key).ElementAt(0);
        }
        catch
        {
          return default(T);
        }
      }
    }
  9. Add the following property to find out those elements that were sought after, but not found in the list:

    ///<summary>
    ///Returns a collection of items that are sought after.
    ///however not found in the list.
    ///</summary>
    public ConcurrentBag<T> ItemsInDemandButNotFound
    {
      get
      {
        return new ConcurrentBag<T>(_innerDS.Where(c => GetFrequency(c.Key) == 0).Select(c=>c.Key));
      }
    }
  10. Add the following method to find out how many times an item is sought:

    ///<summary>
    ///Number of times an item is being sought.
    ///</summary>
    ///<param name="item">The item</param>
    ///<returns>How many times <code>item</code> is sought.</returns>
    public int GetFrequency(T item)
    {
      return _innerDS.GetOrAdd(item, 0);
    }
  11. Add the following public property to return a list of all items sought after:

    ///<summary>
    ///A Concurrent Bag is returned.
    ///This might serve as a concurrent bridge.
    ///</summary>
    public ConcurrentBag<T> Bag
    {
      get
      {
        return new ConcurrentBag<T>(_innerDS.Select(c => c.Key));
      }
    }

    We are now ready to use this API from our application. Let's see how.

    Assume that we are trying to simulate a departmental store, where many people shop in different departments.

    The store management wants to install gigantic electronic boards to show which item is selling the most in each department, as they think this will increase the sales.

    Our just-baked ConcurrentMTFList<T> class is perfect for this situation. I recommend you take a look at the video: http://sudipta.posterous.com/move-to-front-list-most-in-demand-demo. This shows how a move-to-front list works as built in Chapter 8, C5 Collections. In this example, we will try a multi-threaded version of the same.

  12. Create a Windows application and place controls as shown in the following screenshot:

    Time for action – creating the concurrent move-to-front list
  13. Attach a reference of the class library TSList in this project and add the using directives:

    using TSList;
    using System.Threading.Tasks;
    using System.IO;
  14. Add the following variables in Form1.cs:

    ConcurrentMTFList<string> wantedList = new ConcurrentMTFList<string>();
    ConcurrentBag<string> bag = new ConcurrentBag<string>();
    List<Task> shoppers = new List<Task>();
  15. Add the following private methods:

    private void ShopFruits()
    {
      wantedList.Search(bag.ElementAt((new Random()).Next(bag.Count)));
      System.Threading.Thread.Sleep(5000);
    }
    
    private void MakeFruitsAvailable()
    {
      StreamReader fruitListReader = new StreamReader("fruits.txt");
      fruitListReader.ReadToEnd().Split(new char[] { '
    ', '
    ' }, 
      StringSplitOptions.RemoveEmptyEntries)
      .ToList()
      .ForEach(c => wantedList.Add(c));
      fruitListReader.Close();
    }
  16. Add the following code in the Form_Load event:

    private void Form1_Load(object sender, EventArgs e)
    {
      //one task for adding
      //multiple tasks for searching
      wantedList = new ConcurrentMTFList<string>();
      Task.Factory.StartNew(() => MakeFruitsAvailable()).Wait();
      Task.Factory.StartNew(() => wantedList.Bag.ToList()
        .ForEach(c => bag.Add(c))).Wait();
      //Create some virtual shoppers
      for (int i = 0; i < 100; i++)
      {
        Task shopper = new Task(() => ShopFruits());
        shoppers.Add(shopper);            
      }
      //Let these shoppers start shopping
      shoppers.ForEach(shopper => shopper.Start());
    }
  17. Add a timer in the form and enable it with an interval value of 2000.

  18. Add the following code in the timer_Tick event:

    private void timer1_Tick(object sender, EventArgs e)
    {
      textBox1.Text = wantedList.Top + @"is the most in demand fruit now."
                      + Environment.NewLine 
                      +  "It is bought " 
                      + wantedList.GetFrequency(wantedList.Top) + "times.";
    }

We are now ready to run the program. As you run it, you will see a similar output where the text changes after every two seconds:

Time for action – creating the concurrent move-to-front list

I encourage you to check the complete output at http://sudipta.posterous.com/private/nJbnjuwFDx.

What just happened?

In this example, we first created an efficient concurrent move-to-front list and then consumed it to address a real-world use case. The internal data structure of ConcurrentMTFList_innerDS is a ConcurrentDictionary<T,int>.

The Add() method uses the AddOrUpdate() method of ConcurrentDictionary, which is appropriate in a multi-threaded situation. This will prevent the same element from being added more than once.

_innerDS.AddOrUpdate(item, 0, (i, c) => _innerDS[i]=0);

This means adding item as a key with a value of 0 if it is not present, and change its value to 0 if it is already present. Thus, it prevents an automatic accidental increase in the value for any key in the concurrent dictionary.

In the GetFrequency() method, another interesting method called GetOrAdd() is used.

The following code will return the value for the key item if it is present, otherwise it will add it with a key item with a value of 0:

_innerDS.GetOrAdd(item, 0);

The same call is made from within the active search method when an element is not found.

In the Windows application, we used this collection to simulate a grocery store where people are buying fruit and we wanted to determine which fruit is most in-demand. We actually created a task that fills the list of fruits that can be purchased. Then, we created some tasks to represent some virtual shoppers and added them to a list. Note that we have not started them yet. Once we have them all together, we will start them with the following code:

shoppers.ForEach(shopper => shopper.Start());

A delay of five seconds has been added in the ShopFruits() method to prevent making the same fruit available for the next shopper, or in other words, for creating an evenly distributed shopping simulation.

Have a go hero – creating a virtual monitoring system

ConcurrentMTFList has support for finding those elements that were requested, but not found. This can be used to find fruits that people wanted to buy, but are not available. Can you create another task to monitor that? However, you will have to create another task to shoot off few virtual shoppers who will seek something not available unlike the ShopFruits() method, where every shopper wants to buy something from the available bag of fruits.

Time for action – simulating a bank queue with multiple tellers

My bank has a token management system. So as you visit the bank, you have to collect a token. Each token has a number printed on it and the numbers show up against teller counter numbers, on a giant screen. It also shows which token numbers are still to be serviced.

This is a concurrent situation. We can simulate this either with a ConcurrentQueue or with a ConcurrentStack. Let's see how:

  1. Create a Windows application and place controls on the form, as shown in the following screenshot:

    Time for action – simulating a bank queue with multiple tellers

    Also, add the following reference to your project and add them at the start of Form1.cs:

    using System.Collections.Concurrent;
    using System.Threading;
    using System.Threading.Tasks; 
    
  2. Add the following variables and delegate in Form1.cs:

    ConcurrentQueue<string> tickets = new ConcurrentQueue<string>();
    bool stillAddingTickets = false;
    private delegate void ChangeCurrentToken(string oldItem, string newItem);
  3. Add the following methods in Form1.cs to generate random token numbers:

    public string GetTokenNumber()
    {
      List<string> words = new List<string>() 
      { "alpha", "beta", "gamma", "kappa", "delta", "zeta" };
      //A large range, so probability of generating same number gets //reduced.
      List<int> ints = Enumerable.Range(1, 5000).ToList();
      Thread.Sleep(1000);
      return words[new Random().Next(0, words.Count - 1)] + ints[new Random().Next(0, ints.Count - 1)].ToString();
    }
  4. Add the following method to simulate servicing a request by a teller:

    private void ServiceToken(object obj)
    {
      do
      {
        string counter = (string)obj;
        string deq = string.Empty;
    
        tickets.TryDequeue(out deq);
        if (deq != null)
        {
          AddNewToken(txtCounterNumber.Text, deq + " At " + counter);
          System.Threading.Thread.Sleep(2000);
        }
        Thread.Sleep(10000);
      } 
      while (stillAddingTickets || tickets.Count > 0);
    }
  5. Add the following methods to add a generated random token to the concurrent queue:

    private void AddToken()
    {
      string deq = string.Empty;
      for (int i = 0; i < 100; i++)
      {
        stillAddingTickets = true;
        string token = GetTokenNumber();
        tickets.Enqueue(token);
      }
      stillAddingTickets = false;
    }
  6. Note the delegate implementation to change the current displayed token and counter number in the textbox. As there will be multiple threads operating, the task to update the content of the textbox has to be delegated to the UI thread, otherwise it will throw a cross-thread exception:

    private void AddNewToken(string oldItem, string newItem)
    {
      if (this.txtCounterNumber.InvokeRequired)
      {
        //This is a worker thread so delegate the task.
        this.txtCounterNumber.Invoke(new ChangeCurrentToken(this.AddNewToken), oldItem, newItem);
      }
      else
      {
        // This is the UI thread so perform the task.
        try
        {
          txtCounterNumber.Text = newItem;
        }
        catch
        {
          return;
        }
      }
    }
  7. Finally, put all of them to work, create a few tellers and a token generator:

    private void Form1_Load(object sender, EventArgs e)
    {
      Task tokenGenerator = new Task(() => AddToken());
      Task teller1 = new Task((obj) => ServiceToken(obj), "Counter #1");
      Task teller2 = new Task((obj) => ServiceToken(obj), "Counter #2");
      Task teller3 = new Task((obj) => ServiceToken(obj), "Counter #3");
    
      tokenGenerator.Start();//Staring the generator
      teller1.Start();//Starting teller #1
      teller2.Start();//Starting teller #2
      teller3.Start();//Starting teller #3
    }
  8. Add two timers to the project named timer1 and timer3.

  9. Add the following event handlers for the two timers:

    private void timer1_Tick(object sender, EventArgs e)
    {
      listBox1.Items.Clear();
      if (tickets.Count > 0)
      {
        foreach (string pendingToken in tickets)
        {
          listBox1.Items.Add(pendingToken);
        }
      }
    }
    
    private void timer3_Tick(object sender, EventArgs e)
    {
      if (!tickets.IsEmpty)
      {
        lblCount.Text = "There are " + tickets.Count.ToString() + " people in the queue";
      }
      else
      {
        lblCount.Text = "Queue is empty!";
        txtCounterNumber.Text = string.Empty;
      }
    }

We are now ready to run this app. As you run this app, for a moment you will get an output similar to the following screenshot:

Time for action – simulating a bank queue with multiple tellers

I recommend you check out the video that shows this application in action at http://sudipta.posterous.com/queue-simulation-bank-demo.

What just happened?

ConcurrentQueue is a thread-safe version of the plain old queue. As you can imagine, adding elements to a queue in a multi-threaded environment is fine as it does not lead to any exceptional error situation. However, we have to be wary while taking an element out from the collection, because by the time we reach out to get the element, the queue might be empty already.

ConcurrentQueue offers functionalities to do this very easily. The Enqueue() method works as with a normal thread-unsafe generic queue collection. However, the method TryDequeue() returns a null value if the queue is empty, otherwise it returns the first item in the queue.

The queue simulation we built is rudimentary. There is a lot of scope for improvement. One such improvement could be to run another parallel task that will monitor the queue all the time and it would return the next three tokens to be serviced as we have only simulated three tellers. On the other hand, the tellers would like to know how many people are waiting. Moreover, it will be nice if it displayed the expected time before they are attended to by a teller. All these tasks are easily achievable using concurrent collections.

Let's see how we can make the changes.

Time for action – making our bank queue simulator more useful

Follow the given steps:

  1. Add the following two controls in the form, as shown in the following screenshot:

    Time for action – making our bank queue simulator more useful
  2. Change the code in timer1_Tick as shown:

    Replace listBox1.Items.Add(pendingToken); with

    float expectedDelay = tickets.ToList().IndexOf(pendingToken) * 10;
    listBox1.Items.Add(pendingToken +  " Approximate Waiting time " + expectedDelay  + " seconds");
  3. Add the following variable in the Form1 class:

    string pendingStatusTemplate = "Next three tokens are [Queue]";
  4. Add a timer. Name it timer_2 and add the following code to it. Activate the timer and set the time interval for 2 seconds:

    private void timer2_Tick(object sender, EventArgs e)
    {
      lblNextTokens.Text = pendingStatusTemplate.Replace("[Queue]", NextTokens(tickets));
    }
  5. Add the following method:

    private string NextTokens(IProducerConsumerCollection<string> queue)
    {
      StringBuilder nexttokensBuilder = new StringBuilder();
      tickets.Take(3)
             .ToList()
             .ForEach(token => nexttokensBuilder.Append (token + " "));
      return nexttokensBuilder.ToString();
    }

Now if you run the app, you will see an output similar to the following screenshot at any given point in time:

Time for action – making our bank queue simulator more useful

I recommend you check out the video at http://sudipta.posterous.com/private/nhnAmivcCv to see how the program works.

What just happened?

These concurrent collections support LINQ, because they implement the IEnumerable<T> interface. As we have created three tellers, so simultaneously only three people can get their request addressed at three teller locations. However, a little delay is introduced to make sure that the people waiting to get their request serviced don't miss their token number shown on the screen. In the meantime, people find it easier to be told a little in advance that their turn is coming. So, the first three tokens are shown alongside the entire list of pending tokens. This is achieved by using the Take() LINQ operator. Note that the timer runs on a different thread. Even then, the program always returns correct values.

Be a smart consumer, don't wait till you have it all

You have a situation where you want to ensure thread safety for your collection, but you don't care for the order of insertion, or in other words, you don't care whether it is a FIFO or a LIFO list. The elements in the collection can be in any order. In this situation, you need a vanilla implementation of the IProducerConsumerCollection<T> interface and that is BlockingCollection<T>, the wrapper collection of IProducerConsumerCollection<T>. All others except ConcurrentDictionary also implement IProducerConsumerCollection<T>.

There is another situation where BlockingCollection<T> can prove to be extremely handy. While waiting on some producer tasks to generate results and then iterate over them, this might be a bad experience for the user as they have to wait till the producer queues have finished. If we want to perform some other jobs on the results as they start appearing, we can use the GetConsumableEnumerable() method of BlockingCollection<T>.

BlockingCollection<T> also offers some methods to deal with many concurrent collections. We will learn them as a reference in the later chapters.

Exploring data structure mapping

Before we wrap this chapter, let's see what we have learned about thread-safe and thread-unsafe data structure mapping:

Thread-unsafe Generics

Thread-safe Generics

Stack<T>

ConcurrentStack<T>

Queue<T>

ConcurrentQueue<T>

Dictionary<TKey,TValue>

ConcurrentDictionary<TKey,TValue>

List<T>

ConcurrentBag<T>

LinkedList<T>

ConcurrentBag<T>

SortedList<TKey,TValue>

ConcurrentDictionary<TKey,TValue>

There is no direct mapping in the thread-safe collection world that offers SortedList functionalities in a thread-safe way. You can use the LINQ operator OrderBy() lazily, as you need sorting capabilities and use this concurrent dictionary as the basic data structure.

Some of the thread-unsafe generic collections are not present as is; however, with a little bit of thought they can be mapped using the concurrent primitives.

IList<KeyValuePair<TKey,TValue>> can be represented as ConcurrentDictionary<TKey, ConcurrentBag<TValue>>.

SortedDictionary<TKey,TValue> can be represented as ConcurrentDictionary<T, int>(EqualityComparer<T>.Default), where T is the data type.

For example:

ConcurrentDictionary<string, int> conSorted = new ConcurrentDictionary<string, int>(EqualityComparer<string>.Default);

However, this will store the keys in descending order. You can always use the OrderBy() LINQ operator to sort the keys in such a concurrent dictionary in ascending order.

SortedSet<T> doesn't have a direct mapping. However, using the Distinct() and OrderByDescending() operator and ConcurrentBag, we can get a sorted set flavor, as follows:

ConcurrentBag<string> randomWithDuplicates = new ConcurrentBag<string>();
randomWithDuplicates.Add("A");
randomWithDuplicates.Add("A");
randomWithDuplicates.Add("B");
randomWithDuplicates.Add("D");
randomWithDuplicates.Add("C");
randomWithDuplicates.Add("E");

ConcurrentBag<string> set = new ConcurrentBag<string>
(
  randomWithDuplicates

  .Distinct()
  .OrderByDescending(c => c)
);

Summary

We learned about new concurrent collections and how they can be used with the Task Parallelization Library. The things that are not discussed in this chapter include advanced partitioning logic offered by the framework.

In the next chapter, we will learn about power algorithms from Wintellect's Power Collections API. Though with the new .NET Framework most of the generic data structures available there are not relevant any more. However the API is a great place for good algorithms to complement .NET data structures.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.229.172