Parallel Programming in .NET 4.0: Using Tasks

So far in this series, we have discussed PLINQ and the Parallel class.  These allow you to take existing code (LINQ queries, for/foreach loops, etc.) and run it in parallel.  In today’s post, let’s examine how you might leverage .NET 4.0 if you are currently doing lower-level parallel programming using the Thread class. 

Instead of Threads, you can consider using Tasks.  The Task class (in System.Threading.Tasks) allows you to execute asynchronous work.  Among other things, Tasks support waiting, cancellation, continuations, robust exception handling, detailed status, and custom scheduling, which makes them the preferred choice over Threads for parallel programming. 

Let’s walk through some common scenarios with Tasks. 

How do I start a Task?

There are a couple of different ways to start Tasks.  If you want to create and start the task at the same time, use the following code.  (In this case, task 1 simply writes a line of text to the console.) 

 Task task1 = Task.Factory.StartNew(() => Console.WriteLine("Task 1 is doing work."));

How do I chain Tasks together?  

There is great flexibility with Tasks, to be able to chain tasks together so that Task 2 will start after Task 1 completes.  You can also pass results from prior tasks into subsequent tasks. 

 Task.Factory.StartNew(() =>
    {
        // Execute Task 1 work here...
        return "Some Relevant Data";
    }).ContinueWith(t =>
        {
            string result = t.Result;  // can use the result of previous task
            // Execute Task 2 work here...
        }, TaskScheduler.FromCurrentSynchronizationContext());

How do I cancel a Task?   

You also have the ability to cancel running tasks.  This can be useful especially with long-running tasks or tasks performed from a UI with a “Cancel” button for the user.  Imagine a scenario where you use the multiple cores on your machine to run multiple long-running tasks in parallel; you can use the results of whichever task finishes first and easily cancel the rest of them. 

To create a cancelable task, use a CancellationTokenSource and CancellationToken (both in the namespace System.Threading).  Pass the cancellation token into the Task, and in the Task, check if cancellation has been requested.   

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

var task = Task.Factory.StartNew(() =>
{

    // Were we cancelled already, before we started?
    token.ThrowIfCancellationRequested();

    bool moreToDo = true;
    while (moreToDo)
    {
        // Do the actual work here.  Set moreToDo = false when you're done.  

        // Poll on this property if you have to do
        // other cleanup before throwing.
        if (token.IsCancellationRequested)
        {
            // Clean up here, then...
            token.ThrowIfCancellationRequested();
        }
    }
}, tokenSource.Token); // Note that we're passing the same token to StartNew.

From other code, you would call tokenSource.Cancel() to cancel the task when appropriate.  Then the task above would know to respond accordingly. 

Finally, let’s conclude with a sample that uses Tasks from the fabulous collection at https://code.msdn.microsoft.com/ParExtSamples.  (Remember that Stephen Toub gives a great tour through the samples at https://blogs.msdn.com/b/pfxteam/archive/2009/12/09/9934811.aspx.) 

The DiningPhilosophers sample uses Tasks.  If you’re not familiar with the Dining Philosophers problem, in summary, there are five philosophers sitting at a round dinner table.  Each philosopher can do two things: eat or think (and they never do them at the same time).  A fork or chopstick is placed between each pair of philosophers, so each philosopher has a utensil to his left and to his right.  In this problem, we assume that the dinner is spaghetti, so the philosophers need two forks to properly eat (scooping the spaghetti from the bowl…that is why the problem sometimes uses chopsticks instead of forks; it’s more obvious that you need two chopsticks to eat).  This problem illustrates the issue of deadlock, which can easily occur when every philosopher picks up the fork to his left and waits forever for the fork to his right to be available (or vice versa). 

In the code sample, in MainWindow.xaml.cs, there are 3 algorithms for running the problem:

RunWithSemaphoresSyncWithOrderedForks

RunWithSemaphoresSyncWithWaitAll

RunWithSemaphoresAsync

Comment out all but one of these, and run the code. 

DiningPhilosophers

The circles represent the philosophers at the round table.  You will see the philosophers change colors as they switch between thinking, eating, and waiting for forks.  To decode the colors:

Yellow – the philosopher is thinking

Green – the philosopher is eating

Red – the philosopher is waiting for forks

In the individual methods, you will see the use of StartNew() to start the various thinking, eating, and waiting tasks.  You can play with the different algorithms as well. 

For more information on Tasks, there is great documentation at https://msdn.microsoft.com/en-us/library/dd537609.aspx

Stay tuned for tomorrow’s post on the tooling support for parallel programming in Visual Studio 2010.