Using a continuation to chain multiple tasks

Another feature of continuations is that you can continue continuations in order to chain tasks together to any length. The pipeline pattern can be implemented with a series of tasks and continuations. You can think of a pipeline as an assembly line in a factory. At the frontend of a pipeline, a producer task generates the data to be operated on, and each of the chained consumer stages operates on or changes the produced data.

In this recipe we will return to our word count example to create a simple three stage pipeline using continuations with TaskContinuationOptions.OnlyOnRanToCompletion.

How to do it…

Open up Visual Studio, and let's see how to chain tasks together into a pipeline. The steps are as follows:

  1. Start a new project using the C# Console Application project template and assign Continuation7 as the Solution name.
  2. Add the following using directives to the top of your program class:
    using System;
    using System.Linq;
    using System.Net;
    using System.Threading.Tasks;
  3. Let's start this application by adding try/catch blocks in the Main method of the program class. In the catch block add some handling for any AggregateException raised by the tasks. At the end of the catch block, write a message to the console to tell the user we are finished and wait for input to exit.
    try
    {
    //Task and continuations go here
    }
    catch (AggregateException aEx)
    {
        foreach (var ex in aEx.InnerExceptions)
        {
            Console.WriteLine("An exception has occured: {0}", ex.Message);
        }
    }
    Console.WriteLine();
    Console.WriteLine("Complete. Please hit <Enter> to exit.");
    Console.ReadLine();
  4. Now we need to create a producer task that reads in the text of a book, and returns a string array, which the consumer continuations will consume.
    var producer = Task.Factory.StartNew(() =>
    {
        char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', 'u000A' };
        var client = new WebClient();
        const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)";
        client.Headers.Add("user-agent", headerText);
        try
        {
            var words = client.DownloadString(@"http://www.gutenberg.org/files/2009/2009.txt");
            var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
            Console.WriteLine("Word count for Origin of Species: {0}", wordArray.Count());
            Console.WriteLine();
            return wordArray;
        }
        finally
        {
            client.Dispose();
        }
    });
  5. The first consumer will perform a Linq query on the results of the producer to find the five most commonly used words.
    Task<string[]> consumer1 = producer.ContinueWith(antecedent =>
    {
        var wordsByUsage =antecedent.Result.Where(word => word.Length > 5)
            .GroupBy(word => word)
            .OrderByDescending(grouping => grouping.Count())
            .Select(grouping => grouping.Key);
        var commonWords = (wordsByUsage.Take(5)).ToArray();
        Console.WriteLine("The 5 most commonly used words in Origin of Species:");
        Console.WriteLine("----------------------------------------------------");
        foreach (var word in commonWords)
        {
            Console.WriteLine(word);
        }
        Console.WriteLine();
        return antecedent.Result;
    }, TaskContinuationOptions.OnlyOnRanToCompletion);
    The second consumer will perform another Linq query to find the longest word used.
    Task consumer2 = consumer1.ContinueWith(antecedent =>
    {
        var longestWord = (antecedent.Result.OrderByDescending(w => w.Length)).First();
        Console.WriteLine("The longest word is: {0}", longestWord);
    }, TaskContinuationOptions.OnlyOnRanToCompletion);
    consumer2.Wait();
  6. In Visual Studio 2012, press F5 to run the project. You should see output similar to the following screenshot:
    How to do it…

How it works…

The task and continuations we used in this example are pretty much the same as the tasks we have created in other recipes. The primary difference is how we chained them together and the length of the chain. Our antecedent task produces and returns a string array, and then we have a continuation that finds the five most commonly used words, finally we continue the continuation to find the longest word.

Note that we also use TaskContinuationOptions.OnlyOnRanToCompletion because we only want the consumers to be scheduled to run when the previous task succeeded. To be a more complete solution, we would want to use TaskContinuationOptions.OnlyOnFaulted to set up a continuation for the failure path as well.

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

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