Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Using the Task Parallel Library (TPL) in WPF

0.00/5 (No votes)
2 Dec 2013 1  
Keeping a WPF application responsive involves using background processes to handle long running tasks, and the TPL makes this programming task easier than ever. It also provides a way to cancel the task asynchronously and to update the UI (for example, with a progress bar) from the background proces

Sample Image

Introduction

The TPL can be used to keep a desktop application responsive during long duration tasks. Those programmers who have used the BackgroundWorker to keep their application responsive and to provide async cancellation and to update a progress bar from the background process will be able to see how Microsoft's latest incarnation of task scheduling and management provides an even simpler and easier-to-use mechanism for using background tasks in WPF applications.

Background

Before the introduction of the TPL, creating and managing background tasks, especially in WinForm and then WPF applications, required the programmer to handle the code for these tasks. The BackgroundWorker class was able to provide a pre-defined mechanism for providing background and UI task synchronization, but it involved understanding just how the BackgroundWorker provided these capabilities (using the async callback model). The BackgroundWorker was an improvement over handling these programming tasks in an ad-hoc manner, but now a newer, better model is available. All of this functionality is now provided by the new TPL and the CancellationToken mechanism.

Using the code

The code in the simple application accompanying this article demonstrates how to add background processing and a progress bar to a WPF application using the TPL. The application uses an inefficient recursive algorithm to calculate the nth value of the Fibonacci series, a perfect example of a long-running process. A value greater than 40 on the slider bar will take enough time to allow you to cancel the operation before it completes. A slider bar is used to set the the number of the element to calculate in the series. Two buttons are provided, Start Async and Cancel Async, which are self-explanatory. The result of the calculation is displayed in the text box.

The StartAsync button handler contains the code that starts the algorithm in a background process. It also creates the CancellationToken that is passed to the background process to allow it to test for a cancellation request. The actual algorithm is contained in the ComputeFibonacci method. As you can see in the source code, not much needs to be changed to add the async capabilities to the code. To cancel the operation, the CancelAsync handler calls the Cancel method on the CancellationTokenSource like this: tokenSource.Cancel().

Cancellation

The rules for enabling cancellation are:

  1. Create a CancellationTokenSource in the thread that will raise the cancel request, in my example this is the UI thread
  2. Create a CancellationToken and pass it as a parameter to both the long running method (ComputeFibonacci) and the Task running the method (fibTask)

Let's look at some details of the code. We need to create a new instance of the CancellationToken each time we kick-off the process:

tokenSource = new CancellationTokenSource();
var ct = tokenSource.Token;
...

We can pass the cancellation token, ct, to the background processes so they can "cooperatively" process the cancel request.

Creating the background task

Next we need to kick-off the background process, but first we need to save the UI context for synchronization purposes:

//this gets the current UI thread context
var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();
//this creates a new task and starts the process.  Notice we also pass the 
//cancellation token, ct, to both the ComputeFibonacci method and to the Task
Task<long> fibTask = Task.Factory.StartNew(() => ComputeFibonacci(numberToCompute, ct, UISyncContext), ct);
...

Finally, we need to chain a task to display the results of the ComputeFibonacci method, this is one of the features of the TPL:

fibTask.ContinueWith((antecedent) =>
    {
        if (ct.IsCancellationRequested)
        {
          //cleanup here after cancel
          ManageDisplay("Canceled", false);
        }
        else
        {
          ManageDisplay(fibTask.Result.ToString(), false);
        }
    }, UISyncContext);
...

The ContinueWith method chains the new task to the "antecedant" task so that it will automatically run after the previous task completes. Also the second task is automatically synchronized with the UI thread using the UISyncContext that we retrieved earlier, which is passed as a parameter to the ContinueWith method. This is important because you can't update the UI screen in any other thread but the UI thread. The result of the computation is passed back to the UI thread in fibTask's Result property. We can check to see if the operation was canceled by using the cancellation token's IsCancellationRequested property.

Updating the Progress Bar

The rules for updating the progress bar are simple:

  1. Pass the UI thread to the long running method for synchronization
  2. Wrap the progress bar update in a Task that is synchronized with the UI thread

Let's see how that looks:

private long ComputeFibonacci(int n, CancellationToken token, TaskScheduler uiTask)
{
    //check to see if cancel requested, I can do it like this in a Task
    token.ThrowIfCancellationRequested();
    //perform the fibonacci calculation here
    ...
    //check for update to the progress bar
    if (percentComplete > highestPercentageReached)
    {
        highestPercentageReached = percentComplete;
        //this creates a child task for displaying the progress bar updates
        Task.Factory.StartNew(() =>
        {
            progressBar1.Value = highestPercentageReached;
        }, token, TaskCreationOptions.AttachedToParent, uiTask);
    }
}

The first line of code in the ComputeFibonacci method checks to see if a cancellation request has been sent by the user. This is all that is needed to handle the cancel request, the code will not continue past this line if the Cancel Async button has been pressed. Next, this version of the StartNew method takes both a CancellationToken (token) and the synchronization thread (uiTask) as parameters. I don't use the cancellation token, but if I needed to, it is available to this child task as well. The TaskCreateOptions.AttachedToParent parameter says that I want this task to run as a child to the parent thread (ComputeFibonacci). Basically this cancels the child thread automatically when the parent thread is canceled.

That's it! I now have an application that starts a long-running algorithm in the background, that displays progress to the UI and that allows the user to cancel the long-running process asynchronously. These simple steps can be repeated to any level of complexity that is required to accomplish your programming needs.

Points of Interest

Some of you may be thinking, "What in the world is he doing creating and destroying a thread each time he wants to update the progress bar?" Well, the beauty of the TPL is that these are "lightweight" threads that can be instantiated without much overhead. There is very little performance penalty associated with their creation, so you don't need to jump through a lot of hoops to use it. Just create it when you need it, and throw it away when you're done.

History

  • 12/2013 - Initial release.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here