.NET 4.5 Parallel Extensions Cookbook

5 (1 reviews total)
By Bryan Freeman
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Getting Started with Task Parallel Library

About this book

.NET parallel extensions brings the power of parallel and asynchronous programming to a much wider developer audience than ever before. This book will give a developer with no multithreaded development experience the ability to write highly scalable parallel applications that take advantage of modern multicore processors.If you are an experienced .NET developer who wants to put parallel extensions to work in your applications, this book is for you.

".NET 4.5 Parallel Extensions Cookbook" is a practical, hands-on guide that provides you with a number of clear step-by-step recipes that will introduce parallelism into your applications and take advantage of modern multicore processors. This book is a crash course in using the extensions, with theory and concepts kept to a minimum.

".NET 4.5 Parallel Extensions Cookbook" offers a wide-ranging presentation of parallel development concepts, and provides a working knowledge of key technologies that are important to developers who want to take advantage of multi-core architectures.

You will learn how to compose a series of producer/consumer tasks into a pipeline that can process data elements received from a real-time event stream. You will also learn how to connect the stages of pipelines together using the concurrent collections. You will learn everything you need to know to transform the multicore power found in modern processors into application performance and scalability.

Publication date:
July 2013
Publisher
Packt
Pages
336
ISBN
9781849690225

 

Chapter 1. Getting Started with Task Parallel Library

In this chapter, we will cover the following recipes:

  • Creating a task

  • Waiting for tasks to finish

  • Returning results from a task

  • Passing data to a task

  • Creating a child task

  • Lazy task execution

  • Handling task exceptions using try/catch block

  • Handling task exceptions with AggregateException.Handle

  • Cancelling a task

  • Cancelling one of many tasks

 

Introduction


At the beginning of the personal computer era, there was no concept of multiple threads offered by an operating system. Typically, operating system code and application code ran on a single thread of execution. The problem with this was that if a single application misbehaved, or simply took a long time to execute, the whole machine would stall, and often had to be rebooted.

As the development of the Windows operating systems progressed, Microsoft realized that they needed to improve this situation. In the Windows NT kernel, each application runs in its own process. A process is a collection of resources in which a virtual address space is allocated for each application. The advent of these processes ensured that code and data being used by one application could not be accessed and corrupted by another application, thus improving the reliability of the system.

Each process in Windows was also given its own thread. A thread is an operating system construct that functions like a virtual CPU. At any given moment, one of these threads is allowed to run on the physical CPU for a slice of time. When the time for a thread to run expires, it is swapped off of the CPU for another thread. Therefore, if a single thread enters an infinite loop, it can't monopolize all of the CPU time on the system. At the end of its time slice, it will be switched out for another thread.

Over the years, computers with multiple processors began to appear. These multiple processor machines were able to execute multiple threads at once. It became possible for an application to spawn new threads to run a compute-bound process asynchronously, thus gaining a performance improvement.

Over the past few years, the trend in processor development has shifted from making processors faster and faster, to making processors with multiple CPU cores on a single physical processor chip. Individuals who purchase these new machines expect their investment to pay off in terms of applications which are able to run efficiently across the available processor cores. Maximizing the utilization of the computing resources provided by the next generation of multi-core processors requires a change in the way the code is written.

The .NET framework has supported writing multi-threaded applications from the beginning, but the complexity of doing so has remained just out of reach for many .NET developers. To fully take the advantage of multi-threading, you needed to know quite a bit about how Windows works under the hood. For starters, you had to create and manage your own threads, which can be a demanding task as the number of threads in an application grows, and can often be the source of hard-to-find bugs.

Finally, help has arrived. Starting in .NET 4.0, Microsoft introduced the .NET Parallel Extensions, which gave us a new runtime, new class library types (the Task Parallel Library (TPL)), and new diagnostic tools to help with the inherent complexities of parallel programming.

The TPL isn't just a collection of new types. It's a completely new way of thinking about parallel programming. No longer do we need to think in terms of threads. With the TPL, we can now think in terms of task. With this new task-based model, we just need to figure out the pieces of our application that can execute concurrently, and convert those pieces into tasks. The runtime will take care of managing and creating all of the underlying threads that actually do the work. The System.Threading.Task class in itself is just a wrapper for passing a delegate, which is a data structure that refers to a static method or to a class instance, and an instance method of that class.

A TPL task still uses the classic thread pool internally, but the heavy lifting of spinning up new threads to carry out the tasks and determining the optimum number of threads required to take full advantage of the hardware, is all done by the runtime.

In this chapter, we will take a look at the basics of creating a parallel task. You will learn how to pass data into a Task using the Task state object, returning data from a Task, cancelling the Task, and handling exceptions within a Task.

 

Creating a task


Tasks are an abstraction in the .NET framework to represent asynchronous units of work. In some ways, a task resembles the creation of a classic .NET thread, but provides a higher level of abstraction, which makes your code easier to write and read.

We will look at the three basic ways to create and run a new task.

  • The Parallel.Invoke() method: This method provides an easy way to run any number of concurrent statements

  • The Task.Start() method: This method starts a task and schedules it for execution with TaskScheduler

  • The Task.Factory.StartNew() method: This method creates and starts a task using Task.Factory

In this recipe, we will create a new task using each of these three methods. To give our tasks something to do, we will be using WebClient to read the text of three classic books. We will then split the words of each book into a string array, and display a count of the words in each book.

How to do it…

Ok, let's start building a Console application that demonstrates the various ways to create a parallel task.

  1. Launch Visual Studio 2012.

  2. Start a new project using the C# Console Application project template, and assign SimpleTasks as the Solution name as shown in the following screenshot:

  3. Add the following using statements at the top of your Program class:

    using System;
    using System.Linq;
    using System.Net;
    using System.Threading.Tasks;

    Tip

    Downloading the example code

    You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

  4. First, let's create a task using Parallel.Invoke. Add the following code to the Main method of the Program class:

    char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' };
    const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)";
    
    Parallel.Invoke(() =>
        {
        Console.WriteLine("Starting first task using Parallel.Invoke");
        var client = new WebClient();
        client.Headers.Add("user-agent", headerText);
        var words =client.DownloadString(@"http://www.gutenberg.org/files/2009/2009.txt");
        var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
        Console.WriteLine("Origin of Species word count: {0}", wordArray.Count());
        client.Dispose();
        }
    );
  5. Next, let's start task using the Start method of the Task object. Add the following code to the Main method of the Program class just below the code for the previous step:

    var secondTask = new Task(() =>
        {
        Console.WriteLine("Starting second task using Task.Start");
        var client = new WebClient();
        client.Headers.Add("user-agent", headerText);
        var words = client.DownloadString(@"http://www.gutenberg.org/files/16328/16328-8.txt");
        var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
        Console.WriteLine("Beowulf word count: {0}", wordArray.Count());
        client.Dispose();
        } 
      );
    secondTask.Start();
  6. Finally, let's create task using Task.Factory.StartNew. Add the following code to the Main method of the Program class:

    Task.Factory.StartNew(() =>
        {
        Console.WriteLine("Starting third task using Task.Factory.StartNew");
        var client = new WebClient();
        client.Headers.Add("user-agent", headerText);
        var words = client.DownloadString(@"http://www.gutenberg.org/files/4300/4300.txt");
        var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
        Console.WriteLine("Ulysses word count: {0}", wordArray.Count());
        client.Dispose();
        }
    );
    //wait for Enter key to exit
    Console.ReadLine();
  7. In Visual Studio 2012, press F5 to run the project. You should see output similar to the following screenshot. Note that the exact order of the text you see may vary as tasks run asynchronously:

How it works…

The Parallel.Invoke method can implicitly create and run any number of statements concurrently by passing an action delegate for each delegate of work to be done.

Parallel.Invoke(( )=>DoSomething( ), ( )=>DoSomethingElse( ));

It is worth noting however, that the number of tasks actually created by Parallel.Invoke may or may not be equal to the number of delegates passed in, especially if there are a large number of delegates.

Using Task.Start() or Task.Factory.StartNew() creates new tasks explicitly. The new tasks will be allocated threads by the ThreadPool class, which handles the actual creation of the threads the tasks use for carrying out their work. As developers, we are shielded from all of this thread creation work, because it is done for us by the Task object.

When you create a task, you are really just creating a wrapper around a delegate of work to be performed. The delegate can be a named delegate and anonymous method, or a lambda expression.

So, which of these methods of creating task is the best? Task.Factory.StartNew is usually the preferred method, because it is more efficient in terms of the synchronization costs. Some amount of synchronization cost is incurred when using Thread.Start, because it is necessary to ensure that another thread is not simultaneously calling start on the same Task object. When using Task.Factory.StartNew, we know that the task has already been scheduled by the time task reference is handed back to our code.

Note also that you can't call Start() on a task that has already run and completed. If you need the tasks to do the work again, you need to create new task with the same delegate of work.

For the remainder of this book, we will primarily be using Task.Factory.StartNew.

 

Waiting for tasks to finish


When developing a parallel application, you will often have situations where a task must be completed before the main thread can continue processing. The Task Parallel Library includes several methods that allow you to wait for one or more parallel tasks to complete. This recipe will cover two such methods: Task.Wait() and Task.WaitAll().

In this recipe we will be creating three tasks, all of which read in the text classic books and produce a word count. After we create the first task, we will wait for it to complete using Task.Wait(), before starting the second and third task. We will then wait for both the second and third tasks to complete using Task.WaitAll() before writing a message to the console.

How to do it…

Let's create a Console application that demonstrates how to wait for task completion.

  1. Launch Visual Studio 2012.

  2. Start a new project using the C# Console Application project template, and assign WordCount as the Solution name.

  3. Add the following using statements at the top of your Program class:

    using System;
    using System.Linq;
    using System.Net;
    using System.Threading.Tasks;
  4. In the Main method of the Program class, add a character array containing the basic punctuation marks. We will use this array in string.Split() to eliminate punctuation marks. Also add a string constant for the user-agent header of the WebClient.

    char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' };
    const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)";
  5. OK, now let's create our first task. This task will use WebClient to read the Origin of Species by Darwin, and get its word count. Enter the following code in the Main method of the Program class just below the previous statement:

    var task1 = Task.Factory.StartNew(() =>
        {
        Console.WriteLine("Starting first task.");
        var client = new WebClient();
        client.Headers.Add("user-agent", headerText);
        var words = client.DownloadString(@"http://www.gutenberg.org/files/2009/2009.txt");
        var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
        Console.WriteLine("Origin of Species word count: {0}", wordArray.Count());
        }
    );
  6. Now, just below the previous task, write the following statements to wait on the task, and write a message to the Console application:

    task1.Wait();
    Console.WriteLine("Task 1 complete. Creating Task 2 and Task 3.");
  7. Below the previous statement, enter the code to create the second and third tasks. These tasks are very similar to the first task.

    var task2 = Task.Factory.StartNew(() =>
    {
      Console.WriteLine("Starting second task.");
        var client = new WebClient();
        client.Headers.Add("user-agent", headerText);
        var words = client.DownloadString(@"http://www.gutenberg.org/files/16328/16328-8.txt");
        var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
        Console.WriteLine("Beowulf word count: {0}", wordArray.Count());
     });
    
    var task3 = Task.Factory.StartNew(() =>
    {
        Console.WriteLine("Starting third task.");
        var client = new WebClient();
        client.Headers.Add("user-agent", headerText);
        var words = client.DownloadString(@"http://www.gutenberg.org/files/4300/4300.txt");
        var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
        Console.WriteLine("Ulysses word count: {0}", wordArray.Count());
    });
  8. Finally, let's use Task.WaitAll() to wait for the second and third task to complete, then prompt the user to exit the program. Task.WaitAll() takes an array of task as its parameter, and can be used to wait for any number of tasks to complete.

    Task.WaitAll(task2,task3);
    Console.WriteLine("All tasks complete.");
    Console.WriteLine("Press <Enter> to exit.");
    Console.ReadLine();
  9. In Visual Studio 2012, press F5 to run the project. You should see output similar to the following screenshot. Note that the exact order of the last few lines of text may still vary depending on the execution order of the second and third tasks.

How it works…

Although Task.Wait() and Task.WaitAll() are fairly self-explanatory, both have several overloads that offer different functionalities.

Task.Wait() can take either an Int32 or TimeSpan parameter to specify a specific period of time to wait. It can also accept a CancellationToken token parameter for cancellation, which will be covered later in the chapter.

Task.WaitAll() always takes an array of Task as its first parameter, and has a second parameter which can be an Int32 or TimeSpan as in Task.Wait.

Another useful method not shown in the recipe is Task.WaitAny(). WaitAny is very similar to WaitAll, except that it waits for only one Task in the array of Task to complete. The first Task of Task array to finish, completes the wait condition, and execution of the main thread is allowed to move forward.

It is important to note that when you call one of the Wait methods, the runtime will check to see if the task you are waiting on has started executing. If task has started executing, then the thread that called Wait will block until task has finished executing. However, if task has not started running, then the runtime may execute the task using the thread that calls Wait.

The various overloads and behaviors of Task.Wait, Task.WaitAll, and Task.WaitAny are shown in the following table:

Wait()

Waits for the task to complete execution.

Wait(CancellationToken)

Waits for the task to complete execution or CancellationToken to be set.

Wait(Int32)

Waits for task to complete or number of milliseconds to pass. A value of -1 waits indefinitely.

Wait(TimeSpan)

Waits for the task to complete execution or specified timespan to pass.

Wait(Int32, CancellationToken)

Waits for task to complete, number of milliseconds to pass, or CancellationToken to be set.

WaitAll(Task[])

Waits for all of the tasks in array to complete execution.

WaitAll(Task[], Int32)

Waits for all of the tasks in the array to complete execution or number of milliseconds to pass. A value of -1 waits indefinitely.

WaitAll(Task[], CancellationToken)

Waits for all of the tasks in array to complete execution or for a CancellationToken to be set.

WaitAll(Task[], TimeSpan)

Waits for all of the tasks in array to complete execution or specified timespan to pass.

WaitAll(Task[], Int32, CancellationToken)

Waits for all of the tasks in array to complete execution, number of milliseconds to pass, or CancellationToken to be set.

WaitAny(Task[])

Waits for any of the tasks in the array to complete execution.

WaitAny(Task[], Int32)

Waits for any of the tasks in array to complete execution or number of milliseconds to pass. A value of -1 waits indefinitely.

WaitAny(Task[], CancellationToken)

Waits for any of the tasks in array to complete execution or for a CancellationToken to be set.

WaitAny(Task[], TimeSpan)

Waits for any of the tasks in array to complete execution or specified timespan to pass.

WaitAny(Task[], Int32, CancellationToken)

Waits for any of the tasks in array to complete execution, number of milliseconds to pass, or CancellationToken to be set.

 

Returning results from a task


So far, our tasks have not returned any values. However, it is often necessary to return a result from a task so it can be used in another part of our application. This functionality is provided by the Result property of Task<TResult>.

In this recipe, we will be creating a solution similar with tasks similar to the previous solution, but each of our three tasks return a result which can then be used to display the word count to the user.

How to do it…

Let's go to Visual Studio and see how we can return result values from our tasks.

  1. Start a new project using the C# Console Application project template, and assign WordCount2 as the Solution name.

  2. Add the following using statements are at the top of your Program class:

    using System;
    using System.Linq;
    using System.Net;
    using System.Threading.Tasks;
  3. In the Main method of the Program class, add a character array containing the basic punctuation marks. We will use this array in string.Split() to eliminate punctuation marks. Also add a string constant for the WebClient user-agent header.

    char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' };
    const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)";  
  4. Start by creating three tasks of type Task<int> named task1, task2, and task3. Your tasks should look as shown in the following code snippet:

    Task<int> task1 = Task.Factory.StartNew(() =>
    {
        Console.WriteLine("Starting first task.");
        var client = new WebClient();
        client.Headers.Add("user-agent", headerText);
        var words = client.DownloadString(@"http://www.gutenberg.org/files/2009/2009.txt");
        var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
        return wordArray.Count();
    });
    
    Task<int> task2 = Task.Factory.StartNew(() =>
    {
        Console.WriteLine("Starting second task.");
        var client = new WebClient();
        client.Headers.Add("user-agent", headerText);
        var words = client.DownloadString(@"http://www.gutenberg.org/files/16328/16328-8.txt");
        var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
        return wordArray.Count();
    });
    
     Task<int> task3 = Task.Factory.StartNew(()
     {
        Console.WriteLine("Starting third task.");
        var client = new WebClient();
        client.Headers.Add("user-agent", headerText);
        var words = client.DownloadString(@"http://www.gutenberg.org/files/4300/4300.txt");
        var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
        return wordArray.Count();
    });
  5. Immediately below your tasks, add Console.Writeline() statements that use Task.Result to display the results to the user. The remainder of the Main method should now look as shown in the following code snippet:

    Console.WriteLine("task1 is complete. Origin of Species word count: {0}",task1.Result);
    Console.WriteLine("task2 is complete. Beowulf word count: {0}", task2.Result);
    Console.WriteLine("task3 is complete. Ulysses word count: {0}", task3.Result);
    Console.WriteLine("Press <Enter> to exit.");
    Console.ReadLine();
  6. In Visual Studio 2012, press F5 to run the project. You should see output similar to the following:

How it works…

Task<TResult> subclasses the standard Task class and provides the additional feature of the ability to return a value. This is done by switching from providing an Action delegate to providing a Func<TResult> delegate.

It is worth noting that calling the Task.Result accessor will ensure that the asynchronous operation is complete before returning, so this is another method of waiting for a task to complete. Once the result of Task is available, it will be stored and returned immediately on later calls to the Result accessor.

 

Passing data to a task


You can supply the data used by task by passing an instance of System.Action<object> and an object representing the data to be used by the action.

In this recipe, we will be revisiting our WordCount example, but this time we will be parameterizing the data the tasks will act upon.

How to do it…

The ability to pass data into a task allows us to create a single task that can operate on multiple pieces of input data. Let's create a Console application so we can see how this works:

  1. Start a new project using the C# Console Application project template and assign WordCount3 as the Solution name.

  2. Add the following using statements at the top of your Program class:

    using System;
    using System.Linq;
    using System.Net;
    using System.Threading.Tasks;
    using System.Collections.Generic;
  3. In the Main method of the Program class, add a character array containing the basic punctuation marks. We will use this array in string.Split() to eliminate punctuation marks. Also add a constant string for the WebClients user-agent task.

    char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' };
    const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)";
  4. For this recipe, let's create a new Dictionary instance that can hold our book titles and URLs. Immediately after the previous statement, add the following code to create and initialize the dictionary:

    var dictionary = new Dictionary<string, string>
    {
        {"Origin of Species", "http://www.gutenberg.org/files/2009/2009.txt"},
        {"Beowulf", "http://www.gutenberg.org/files/16328/16328-8.txt"},
        {"Ulysses", "http://www.gutenberg.org/files/4300/4300.txt"}
    };
  5. This time we will be creating anonymous tasks in a loop. We still would like to wait for the tasks to complete before prompting the user to exit the program. We need a collection to hold our tasks, so we can pass them to Task.WaitAll() and wait for completion. Below the previous statement, create a List<Task> to hold our tasks.

    var tasks = new List<Task>();
  6. Next, we want to create a for loop to loop through KeyValuePairs in the dictionary. Let's put the for loop below the previous statement.

    foreach (var  pair in dictionary)
    {
    }
  7. Inside the body of your for loop, put the definition of task, and add it to your task list as follows. Note the KeyValuePair being passed into task is in the form of an object. In the delegate body, we cast this object back to a KeyValuePair. Other than that, task is pretty much the same.

    tasks.Add( Task.Factory.StartNew((stateObj) =>
    {
        var taskData = (KeyValuePair<string, string>)stateObj;
        var client = new WebClient();
        client.Headers.Add("user-agent", headerText);
        var words = client.DownloadString(taskData.Value);
        var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
        Console.WriteLine("Word count for {0}: {1}", taskData.Key, wordArray.Count());
    },pair));
  8. After the for loop, let's finish things up by waiting on the tasks to complete using Task.WaitAll() and prompting the user to exit. The last few lines should be as follows:

    Task.WaitAll(tasks.ToArray());
    Console.WriteLine("Press <Enter> to exit.");
    Console.ReadLine();
  9. In Visual Studio 2012, press F5 to run the project. You should see output as shown in the following screenshot:

How it works…

By passing data to Task using the state feature, we now have a very powerful model for task creation, because we can create many tasks at once, each having the same code statements in the body and passing in the data that Task operates on. It also makes our code much more concise and readable.

In our application we need to pass two items of data into the task: a book title and the URL of the book, so we created dictionary.

var dictionary = new Dictionary<string, string>
{
    {"Origin of Species", "http://www.gutenberg.org/files/2009/2009.txt"},
    {"Beowulf", "http://www.gutenberg.org/files/16328/16328-8.txt"},
    {"Ulysses", "http://www.gutenberg.org/files/4300/4300.txt"}
};

We would also want to wait on all of these tasks to complete before we prompt the user to exit, so we need to create a collection that can be converted to an array of tasks to hold our Task objects. In this case, we made a list of tasks. In the body of our look that creates the tasks, we will add the tasks to the list.

var tasks = new List<Task>();

foreach (var pair in dictionary)
{
    tasks.Add( //TASK DECLARATION HERE   ));
}

In our loop, we will pass in each of KeyValuePairs in dictionary as an object, using the Task(Action<Object>, Object) constructor. This syntax is just a bit odd because you actually refer to the state object twice.

Task.Factory.StartNew((stateObj) =>
{
    // TASK Body
},pair ));}

The key takeaway here is that the only way to pass data to a Task constructor is using Action<Object>. To use the members of a specific type, you must convert or explicitly cast the data back to the desired type in the body of the Task.

var taskData = (KeyValuePair<string, string>)stateObj;
 

Creating a child task


Code that is running a task can create another task with the TaskCreationOptions.AttachedToParent set. In this case, the new task becomes a child of the original or parent task.

In this recipe, we will be using a simplified version of the WordCount solution that uses a parent task to get the text of one book into a string array, and then spins up a child task to print the results.

How to do it…

Let's return to our WordCount solution, so we can see how to create a child task and attach it to a parent.

  1. Start a new project using the C# Console Application project template, and assign WordCount4 as the Solution name.

  2. Add the following using statements at the top of your Program class:

    using System;
    using System.Linq;
    using System.Net;
    using System.Threading.Tasks;
  3. In the Main method of the Program class, add a character array containing the basic punctuation marks. We will use this array in string.Split() to eliminate punctuation marks. Also, add a constant string for the WebClient user-agent header.

    char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' };
    const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)";
  4. First, let's create the basic structure of our parent task. This is very similar to the other tasks we have created so far, and takes no parameters, and returns no values.

    Task parent = Task.Factory.StartNew(() =>
    {
        Console.WriteLine("Parent task starting");
        const string uri = "http://www.gutenberg.org/files/2009/2009.txt";
        var client = new WebClient();
        client.Headers.Add("user-agent", headerText);
        var book = client.DownloadString(uri);
        var wordArray = book.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
        // Child Task will go here
    });
  5. Next, right after the comment in the parent task, let's create a child task to print the results and set the AttachedToParent option.

    Task.Factory.StartNew(()=>
    {
        Console.WriteLine("Child task starting");
        Console.WriteLine("Word count for Origin of Species: {0}",wordArray.Count());
        Console.WriteLine("Attached child task completed.");
    },TaskCreationOptions.AttachedToParent);
  6. Finally, just below the close of the parent task, let's wait for the parent task to complete, and prompt the user to exit the application with the following code:

    parent.Wait();
    Console.WriteLine("Parent task completed.");
    Console.WriteLine("Press <Enter> to exit.");
    Console.ReadLine();
  7. That's pretty much it. In Visual Studio 2012, press F5 to run the project. You should see output as shown in the following screenshot:

How it works…

Using TaskCreationOptions.AttachedToParent expresses structured parallelism. The parent task will wait for the child task to finish, so at the end of our program, all we have to do is wait for the parent task.

The nested child task, itself, is just an ordinary task created in the delegate of another task. A parent task may create any number of child tasks, limited only by system resources.

You can also create a nested task without using TaskCreationOptions.AttachedToParent. The only real difference is that the nested tasks created without this option are essentially independent from the outer task. A task created with the TaskCreationOptions.AttachedToParent option set is very closely synchronized with the parent.

The outer task could also use the DenyChildAttach option to prevent other tasks from attaching as child tasks. However, the same outer task could still create an independent nested task.

 

Lazy task execution


Lazy initialization of an object means that object creation is deferred until the object is actually used by your program. If you have a parallel task that you want to execute only when the value returned from the task is actually needed, you can combine lazy task execution with the Task.Factory.StartNew method.

In this recipe, we will return to our, by now familiar WordCount solution, to show you how to execute a parallel task and compute a word count for our book, only when we display the result to the console.

How to do it…

Let's create a console application that demonstrates how we can defer task creation until the result of the task is needed.

  1. Start a new project using the C# Console Application project template, and assign WordCount5 as the Solution name.

  2. Add the following using statements at the top of your Program class.

    using System;
    using System.Linq;
    using System.Net;
    using System.Threading.Tasks;
  3. The first step is to declare System.Threading.Task<int> for lazy initialization. In the Main method of your Program class, put a Lazy declaration as follows:

    var lazyCount = new Lazy<Task<int>>(()=
    {
      //Task declaration and body go here
    });
  4. Inside the Lazy initialization declaration, place the code to create to task. The entire statement should now look as the following code snippet:

    Task<int>.Factory.StartNew(() =>
    {
        Console.WriteLine("Executing the task.");
        char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' };
        const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)";
        const string uri = "http://www.gutenberg.org/files/2009/2009.txt";
        var client = new WebClient();
        client.Headers.Add("user-agent", headerText);
        var words = client.DownloadString(uri);
        var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
        return wordArray.Count();
    }));
  5. Now we just need to write the result to the Console. Just add the following code to the end of your program:

    Console.WriteLine("Calling the lazy variable");
    Console.WriteLine("Origin of species word count: {0}",lazyCount.Value.Result );
    Console.WriteLine("Press <Enter> to exit.");
    Console.ReadLine();
  6. All done. In Visual Studio 2012, press F5 to run the project. You should see the output as shown in the following screenshot:

How it works…

System.Lazy<T> creates a thread safe Lazy initialization of an object. Lazy initialization is primarily used to improve performance and avoid computational overhead until necessary. You can pass a delegate (remember that System Threading Task is just a wrapper around a delegate) to the System.Lazy constructor, and as we have done in this recipe, you can use a lambda expression to specify a factory method for object creation. This keeps all of the initialization code in one place.

Lazy initialization occurs the first time the System.Lazy<T>.Value property is accessed.

 

Handling task exceptions using try/catch block


Let's face it; sometimes things just go wrong with our code. Even with the simplified parallel programming model provided by the TPL, we still need to be able to handle our exceptions.

Tasks use System.AggregateException to consolidate multiple failures into a single exception object. In this recipe, we will take a look at the simplest way to handle System.AggregateException in our tasks: the try/catch blocks.

The try-catch statement consists of a try block followed by one of more catch blocks, which specify handlers for different exceptions. The try block contains the guarded code that may cause the exception.

Getting ready…

For this recipe we need to turn off the Visual Studio 2012 Exception Assistant. The Exception Assistant appears whenever a runtime exception is thrown, and intercepts the exception before it gets to our handler.

  1. To turn off the Exception Assistant, go to the Debug menu and select Exceptions.

  2. Uncheck the user-unhandled checkbox next to Common Language Runtime Exceptions.

How to do it…

Let's return to our WordCount solution so we can see how to handle an AggregateException thrown by a parallel task.

  1. Start a new project using the C# Console Application project template and assign WordCount6 as the Solution name.

  2. Add the following using statements are at the top of your Program class:

    using System;
    using System.Linq;
    using System.Net;
    using System.Threading.Tasks;
  3. For this recipe, we will just need a single task. The task will be very similar to our other word count tasks, but in this one we will simulate a problem with the System.Net.WebClient by creating and throwing a System.Net.WebException. In the Main method of your Program class, create System.Task that looks as the following Task:

    Task<int> task1 = Task.Factory.StartNew(() =>
    {
        const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)";
        Console.WriteLine("Starting the task.");
        var client = new WebClient();
        client.Headers.Add("user-agent", headerText);
        var words = client.DownloadString(@"http://www.gutenberg.org/files/2009/2009.txt");
        var ex = new WebException("Unable to download book contents");
        throw ex;
        return 0;
    });
  4. Just below the Task, let's put in our try/catch blocks as shown in the following code snippet. In the catch block, we will want to specifically catch System.AggregateException.

    try
    {
    }
    catch (AggregateException aggEx)
    { 
    }
  5. Now let's implement the body of our try block. The body of the try block should be as shown in the following code snippet. There are a couple of subtle but important concepts in here that will be explained later in the chapter.

    try
    {
        task1.Wait();
        if (!task1.IsFaulted)
        {
         Console.WriteLine("Task complete. Origin of Species word count: {0}",task1.Result);
        }
    }
  6. Next, let's implement the body of our catch block. It should look as shown in the following code snippet:

    catch (AggregateException aggEx)
    {
        foreach (var ex in aggEx.InnerExceptions)
        {
          Console.WriteLine("Caught exception: {0}", ex.Message);
        }
    }
  7. After the catch block, let's finish up by prompting the user to exit, and waiting on the user to hit Enter.

    Console.WriteLine("Press <Enter> to exit.");
    Console.ReadLine();
  8. In Visual Studio 2012, press F5 to run the project. You should see output as shown in the following screenshot:

How it works…

All of this stuff has been pretty self-explanatory so far, but handling exceptions in task involves a couple of subtleties that need to be pointed out.

The task itself is pretty straightforward. Other than throwing the System.Net.WebException, there is nothing out of the ordinary here.

Let's take a closer look at the try/catch blocks. The first statement in the try block System.Threading.Task.Wait() to wait on task completion. However, there is another purpose here. Unhandled exceptions thrown inside a task are swallowed by the runtime and wrapped up in System.AggregateException. It is your job to handle this.

The TPL also has the concept of AggregateException being observed. If AggregateException is raised by your task, it will only be handled if it is currently being observed. This is very important to understand. If you never take an action that causes the exceptions to be observed, you are going to have a problem. When the Task object is garbage collected, the Finalize method of the task will see that the task had unobserved exceptions, and it will throwSystem.AggregateException. You will not be able to catch an exception thrown by the finalizer thread and your process will be terminated.

So how to you observe an AggregateException, you ask? The Systm.Threading.Task class has a few methods and properties call triggers that cause System.AggregateException to be observed. A few of these are as follows:

  • Task.Wait

  • Task.WaitAny

  • Task.WaitAll

  • Task.Result

Using any of these trigger methods indicates to the runtime that you are interested in observing any System.AggregateException that occurs. If you do not use one of the trigger methods on the Task class, the TPL will not raise any AggregateException, and an unhandled exception will occur.

Now, let's take a look at the catch block. System.AggregateException can wrap many individual exception objects. In our catch block, we need to loop through AggregateException.InnerExceptions to take a look at all of the individual exceptions that occurred in a task.

It is important to note that there is really no way to correlate an exception from the AggregateExcetion.InnerExceptions collection back to the particular task that threw an exception. All you really know is that some operation threw an Exception.

System.AggregateException overrides the GetBaseException method of exception, and returns the innermost exception, which is the initial cause of the problem.

 

Handling task exceptions with AggregateException.Handle


In this recipe, we will look at another way to handle System.AggregateException, by using the AggregateException.Handle method. The Handler method invokes a handler function for each exception wrapped in AggregateException.

Getting ready…

For this recipe, we need to turn off the Visual Studio 2012 Exception Assistant. The Exception Assistant appears whenever a runtime exception is thrown and intercepts the exception before it gets to our handler.

  1. To turn off the Exception Assistant, go to the Debug menu and select Exceptions.

  2. Uncheck the user-unhandled checkbox next to Common Language Runtime Exceptions.

How to do it…

Let's take a look at how we can use AggregateException.Handle to provide an alternate method to handling exceptions in a parallel application.

  1. For this recipe, we will return to our WordCount6 project, and modify it to handle our exceptions in a different way. Start Visual Studio 2012 and open the WordCount6 project.

  2. The first step is to define our handler function that will be invoked when we call AggregateException.Handle. Following the Main method of your Program class, add a new private static handler method that returns a bool. It should look as the following code snippet:

    private static bool HandleWebExceptions(Exception ex)
    {
        if (ex is WebException)
        {
          Console.WriteLine(("Caught WebException: {0}", ex.Message);
          return true;
        }
        else
        {
          Console.WriteLine("Caught exception: {0}", ex.Message);
          return false;
        }
    }
  3. The only other step here is to replace the body of your catch block with a call to System.AggregateException.Handle, passing in the HandleWebExceptions predicate. The updated try/catch block should look as follows:

    try
    {
        task1.Wait();   
        if (!task1.IsFaulted)
        {
          Console.WriteLine("Task complete. Origin of Species word count: {0}",task1.Result);
        }
    }
    catch (AggregateException aggEx)
    {
        aggEx.Handle(HandleWebExceptions);                
    }
  4. Those are the only modifications necessary. In Visual Studio 2012, press F5 to run the project. You should see output as shown in the following screenshot:

How it works…

AggregateException.Handle() takes a predicate that you supply, and the predicate will be invoked once for every exception wrapped in System.AggregateException.

The predicate itself just needs to contain the logic to handle the various exception types that you expect, and to return true or false to indicate whether the exception was handled.

If any of the exceptions went unhandled, they will be wrapped in a new System.AggregateException and thrown.

 

Cancelling a task


Up to this point, we have focused on creating, running, and handling exceptions in tasks. Now we will begin to take a look at using System.Threading.CancellationTokenSource and System.Threading.CancellationToken to cancel tasks.

This recipe will show how to cancel a single task.

How to do it…

Let's create a console application that shows how to cancel a parallel task.

  1. Start a new project using the C# Console Application project template, and assign WordCount7 as the Solution name.

  2. Add the following using statements at the top of your Program class:

    using System;
    using System.Linq;
    using System.Net;
    using System.Threading;
    using System.Threading.Tasks;
  3. Let's start by creating CancellationTokenSource and getting our CancellationToken. In the Main method of your Program class, add the following statements:

    //Create a cancellation token source
    CancellationTokenSource tokenSource = new CancellationTokenSource();
    //get the cancellation token
    CancellationToken token = tokenSource.Token;
  4. Now we need to create our Task and pass CancellationToken into the constructor. Right after the previous line, put in the following Task definition:

    Task<int> task1 = Task.Factory.StartNew(() =>
    {
        // The body of the task goes here                
    }, token);  
  5. In the body of our Task, we need to check the IsCancellationRequested property of CancellationToken. If it has been, we dispose of our resource and throw OperationCancelledException. If not, we do our usual work. Enter the following code into the body of Task:

    //wait a bit for the cancellation
    Thread.Sleep(2000);
    const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)";
    var client = new WebClient();
    client.Headers.Add("user-agent", headerText);
    if(token.IsCancellationRequested)
    {
        client.Dispose();
        throw new OperationCanceledException(token);
    }
    else
    {
        var book = client.DownloadString(@"http://www.gutenberg.org/files/2009/2009.txt");
        char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' };
        Console.WriteLine("Starting the task.");
        var wordArray = book.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
        return wordArray.Count();
    }
  6. Right after the task, put in the following lines to write the cancellation status to the Console, and then call the Cancel method of TokenSource.

    Console.WriteLine("Has the task been cancelled?: {0}", task1.IsCanceled);
    //Cancel the token source
    tokenSource.Cancel();
  7. The following are the last statements put in a condition to check whether the task has been cancelled or faulted before we try to write out the results:

    if (!task1.IsCanceled || !task1.IsFaulted)
    {
        try
        {
            if (!task1.IsFaulted)
            {
              Console.WriteLine("Origin of Specied word count: {0}", task1.Result);
            }
        }
        catch (AggregateException aggEx)
        {
        foreach (Exception ex in aggEx.InnerExceptions)
        {
              Console.WriteLine("Caught exception: {0}", ex.Message);
        }
        }
    }
    else
    {
        Console.WriteLine("The task has been cancelled");
    }
  8. Lastly, we'll finish up by prompting the user to exit and waiting for the input.

    Console.WriteLine("Press <Enter> to exit.");
    Console.ReadLine();
  9. In Visual Studio 2012, press F5 to run the project. You should see the output as shown in the following screenshot:

How it works…

The basic idea of cancelling task is that we create CancellationTokenSource, obtain CancellationToken from it, and then pass CancellationToken onto the Task constructor. Once we have done that, we can call the Cancel method on the CancellationTokenSource to cancel the task.

That's all easy enough. However, inside task we have a couple of options on how to handle the cancellation.

If your task has resources that need to be cleaned up (such as the WebClient), you need to check the cancellation tokens IsCancellationRequested property, then dispose of the resources, and throw a new OperationCancelledException.

The other option, if your task doesn't use resources which need to be explicitly cleaned up, is to use the token ThrowIsCancellationRequested(), which will ensure the task transitions to a status of cancelled in a single statement.

If you need to execute task and prevent it from being cancelled, you can obtain a special CancellationToken that is not associated with any CancellationTokenSource from the static CancellationToken.None property, and pass this token to Task. Since there is no associated CancellationTokenSource, it is not possible to call Cancel for this token, and any code that is checking the CancellationToken.IsCancellationRequested property will always get back a false.

There's more…

You can register one or more methods to be called when CancellationTokenSource is cancelled, by registering a callback method with the CancellationToken.Register method. You just need to pass Action<Object>, and optionally, a state value will be passed to callback and a Boolean, indicating whether to invoke the delegate using SynchronizationContext of the calling thread to the Register method. Passing in a value of false means the thread that calls Cancel will invoke the registered methods synchronously. If you pass true, the callbacks will be sent to SynchronizationContext, which determines which thread will invoke the callbacks.

var source = new CancellationTokenSource();
source.Token.Register(()=>
{
  Console.WriteLine("The operation has been cancelled.");
});

The Register method of CancellationToken returns a CancellationTokenRegistration object. To remove a registered callback from CancellationTokenSource, so it doesn't get invoked, call the Dispose method of the CancellationTokenRegistration object.

You can also create CancellationTokenSource by linking other CancellationTokenSource objects together. The new composite CancellationTokenSource will be cancelled, if any of the linked CancellationTokenSource objects are cancelled.

var source1 = new CancellationTokenSource();
var source2 = new CancellationTokenSource();
var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(source1.Token, source2.Token);
 

Cancelling one of many tasks


Now that we have seen how to cancel a task, let's take a look at how we can use CancellationToken to cancel multiple tasks with a single call to CancellationTokenSource.Cancel().

How to do it…

Now let's return to our WordCount example and create a Console application that provides for the cancellation of multiple tasks with single CancellationToken.

  1. Start a new project using the C# Console Application project template, and assign WordCount8 as the Solution name.

  2. Add the following using statements at the top of your Program class:

    using System;
    using System.Linq;
    using System.Net;
    using System.Threading;
    using System.Threading.Tasks;
  3. First, let's create a helper method to display errors and cancellation status. Since we have multiple tasks, it's better to have this logic all in one place. Following the Main method of your Program class, create a static method call HandleExceptions which will display the errors and task status to the user.

    private static void DisplayException(Task task, AggregateException outerEx, string bookName)
    {
        foreach (Exception innerEx in outerEx.InnerExceptions)
        {
          Console.WriteLine("Handled exception for {0}:{1}",bookName,innerEx.Message);
        }
        Console.WriteLine("Cancellation status for book {0}: {1}", bookName, task.IsCanceled);
    }
  4. Next, at the top of the Main method, create CancellationTokenSource and get our CancellationToken.

    //Create a cancellation token source
    CancellationTokenSource tokenSource = new CancellationTokenSource();
    //get the cancellation token
    CancellationToken token = tokenSource.Token;
  5. Now we need to create our tasks and our array of delimiters. The tasks are the same as in the recipe for cancelling a single task. The key here is that we are passing the same CancellationToken in for all three tasks.

    char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' };
    const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)";
    
     Task<int> task1 = Task.Factory.StartNew(() =>
    {
        // wait for the cancellation to happen
        Thread.Sleep(2000);
        var client = new WebClient();
        client.Headers.Add("user-agent", headerText);
        if (token.IsCancellationRequested)
        {
          client.Dispose();
          throw new OperationCanceledException(token);
        }
        else
        {
          var words = client.DownloadString(@"http://www.gutenberg.org/files/2009/2009.txt");
          Console.WriteLine("Starting the task for Origin of Species.");
          var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
          return wordArray.Count();
        }
    },token);
    
    
     Task<int> task2 = Task.Factory.StartNew(() =>
    {
        // wait for the cancellation to happen
        Thread.Sleep(2000);
        var client = new WebClient();
        client.Headers.Add("user-agent", headerText);
        if (token.IsCancellationRequested)
        {
          client.Dispose();
          throw new OperationCanceledException(token);
        }
        else
        {
          var words = client.DownloadString(@"http://www.gutenberg.org/files/16328/16328-8.txt");
          Console.WriteLine("Starting the task for Beowulf.");
          var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
          return wordArray.Count();
        };
    },token);
    
    Task<int> task3 = Task.Factory.StartNew(() =>
    {
        // wait for the cancellation to happen
        Thread.Sleep(2000);
        var client = new WebClient();
        client.Headers.Add("user-agent", headerText);
        if (token.IsCancellationRequested)
        {
          client.Dispose();
          throw new OperationCanceledException(token);
        }
        else
        {
          var words = client.DownloadString(@"http://www.gutenberg.org/files/4300/4300.txt");
          Console.WriteLine("Starting the task for Ulysses.");
          var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
          return wordArray.Count();
        };
    },token);
  6. OK, let's finish up by calling CancellationTokenSource.Cancel() , checking the results, and catching the exceptions. The remainder of the Main method should look as the following code snippet:

    //Cancel the token source
    tokenSource.Cancel();
    try
    {
        if (!task1.IsFaulted || !task1.IsCanceled)
        {
          Console.WriteLine("Origin of Specied word count: {0}", task1.Result);
        }
    }
    catch(AggregateException outerEx1)
    {
        DisplayException(task1, outerEx1, "Origin of Species");
    }
    try
    {
        if (!task2.IsFaulted || !task2.IsCanceled)
        {
          Console.WriteLine("Beowulf word count: {0}", task2.Result);
        }
    }
    catch (AggregateException outerEx2)
    {
        DisplayException(task2, outerEx2, "Beowulf");
    }
    try
    {
        if (!task3.IsFaulted || !task3.IsCanceled)
        {
          Console.WriteLine("Ulysses word count: {0}", task3.Result);
        }
    }
    catch (AggregateException outerEx3)
    {
       DisplayException(task3, outerEx3, "Ulysses");
    }
    Console.ReadLine();
  7. In Visual Studio 2012, press F5 to run the project. You should see output as shown in the following screenshot:

How it works…

Functionally, cancelling multiple tasks is the same as cancelling a single task. In fact, the Parallel Extensions team has put a lot of work into making cancellation of various parallel structures very similar, as you will see as we go through the book.

All that is necessary to cancel multiple tasks is to create CancellationToken, then pass that token into all of the tasks you wish to cancel as shown in the following code snippet:

CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;

Task<int> task1 = Task.Factory.StartNew(() =>
{
  ...
},token);
Task<int> task, = Task.Factory.StartNew(() =>
{
  ...
},token);

tokenSource.Cancel();

About the Author

  • Bryan Freeman

    Bryan Freeman is a developer, architect, and writer on Parallel and Distributed Technologies. With over 15 years of experience delivering solutions across industry sectors such as healthcare, finance, and transportation, he specializes in technologies such as Microsoft .NET, Windows Communication Foundation, Windows Workflow, and Windows Azure to help clients deliver business value and drive revenue while reducing operational costs. As an active speaker, writer, and blogger, Bryan is a passionate community advocate for those who develop solutions based on .NET technologies. He is also an active member of the Scottsdale Institute and the Veterans of Foreign Wars.

    Browse publications by this author

Latest Reviews

(1 reviews total)
Exactly what I was looking for!
Book Title
Access this book, plus 7,500 other titles for FREE
Access now