The blog of dlaa.me

Nobody likes seeing the hourglass... [Keep your application responsive with BackgroundTaskManager on WPF and Silverlight]

In my last post I talked about the source control repository browser project I've been playing around with and mentioned that one of my primary goals was for the application to be "Completely asynchronous so that one or more long-running network tasks wouldn't hang the user interface". Some of you asked me to go into more detail about how this application works, and I thought I'd begin by discussing the asynchronous aspect.

The challenge here is that sometimes an application needs to perform a long-running task and it doesn't want to do so on the user interface thread because that causes noticeable delays and those make users grumpy. (Any time you see an application become completely unresponsive and stop updating its window, that's probably because the UI thread is busy doing something other than updating the UI...)

Fortunately, there's usually an API available for potentially long-running tasks that is written according to one of the common asynchronous programming design patterns. When that's the case, making use of the provided construct is almost always the preferred option because the API's authors are in the best position to provide the right abstractions and interfaces. However, sometimes you'll find that the only API or technique for accomplishing what you need is synchronous. In these cases, the best choice is usually to move as much of the operation as possible to a separate thread in order to avoid bogging down the UI thread.

BackgroundTaskManagerDemo on WPF

.NET provides a number of ways to accomplish this, one of which is the BackgroundWorker class. BackgroundWorker tries to make asynchronous operations easy and - while it's not something I find myself using often - is well suited to the problem at hand! Specifically, all that's needed to avoid hanging the UI is to push the brunt of the work to the DoWork event and then add a bit of code to the RunWorkerCompleted event to take the result of that work and update the user interface accordingly.

Aside: There are at least two high level approaches to updating the user interface after a long-running task: by updating the (bound) data and/or view model with the new values or by manipulating the UI directly to show those values. Both approaches work, but one of the things I find very elegant about the WPF and Silverlight architecture is how well suited it is to supporting rich data binding across an entire application. In particular, one of the great things that comes from an application which makes strong use of data binding is that there's typically very little need to directly manipulate the UI. Which means there's also a great deal less code that needs to be written to keep track of the exact state of the UI and make sure that overlapping updates don't conflict with each other.

Because when you're managing all the UI yourself, you need to be sure that the results of a background task are still relevant by the time they're available (for example, that the user hasn't changed tasks, reconfigured the window, etc.). But when you're using data binding and updating the model the UI is bound to, the right thing "just happens" without any special effort on your part. If the new data is still relevant, it shows up exactly where it's supposed to - and if it's no longer applicable, it just doesn't show up. And if the user switches back to what they were doing, the new data is automatically used.

Synchronization can be tricky to get right, so being able to avoid it entirely by taking advantage of WPF and Silverlight's rich data binding support is a big win!

 

For the purposes of my source control repository viewer application, every call to the local source control client (ex: TF.exe or SVN.exe) is a synchronous call and is assumed to take a long time to complete. Granted, in the best case, a particular call might not need to contact the server and will complete quickly - but in the worst case, the call to the server ends up getting queued behind a backlog of other long-running commands and takes tens of seconds (or more!) to complete. I obviously don't want the interface of my application to block during this time - and furthermore I don't want to prevent the user from performing other operations just because a particular one is taking a long time to complete. For example, the user may have just requested a recursive history of the entire repository and is fully expecting for the command to take a while to complete. But there's no reason that should prevent him or her from simultaneously initiating a much simpler request to do something like view the list of pending changes. Therefore, every call to the source control client is run on a background thread and when the results come back they are used to update the model - at which point pervasive data binding automatically propagates those changes to the user interface!

BackgroundTaskManagerDemo on Silverlight

It's a fairly simple programming model, but a powerful one, and it leaves the application's UI snappy and responsive no matter what the user does, how fast the network is, or how overloaded the server may be. :)

 

All long-running tasks are initiated via the BackgroundTaskManager class which is a thin wrapper around BackgroundWorker. The additional functionality this wrapper provides is tracking of the number of active background tasks (which is automatically reflected in the UI to help the user understand what's going on) and a slightly more convenient interface for specifying the code that runs in the background and upon completion. Here's what a typical call to BackgroundTaskManager from the UI thread looks like (from the sample application):

BackgroundTaskManager.RunBackgroundTask(
    () =>
    {
        // Task function runs in the background
        return ComputeValue(TimeSpan.FromSeconds(durationInSeconds));
    },
    (result) =>
    {
        // Completion function runs on the UI thread
        UseValue(result);
        Log(string.Format(CultureInfo.CurrentCulture,
            "Finished asynchronous task taking {0} seconds", durationInSeconds));
    });

The two methods that get passed to RunBackgroundTask can do anything you want - with one important restriction: the background task function must not do anything that directly manipulates the user interface. This is a platform limitation that anyone who's done much threading with WPF or Silverlight is probably familiar with: only the UI thread is allowed to manipulate the UI. So the pattern I follow is that the background task's only responsibility is to perform the time-consuming operation (ex: the call to the source control server). The result of this computation is handed directly to the completion function which has the responsibility of updating the UI either directly or through the model. It's an easy model to develop against and has proven pretty valuable so far.

Here's the complete implementation of BackgroundTaskManager:

/// <summary>
/// Class that manages the execution of background tasks.
/// </summary>
public static class BackgroundTaskManager
{
    /// <summary>
    /// Event invoked when a background task is started.
    /// </summary>
    [SuppressMessage("Microsoft.Usage", "CA2211:NonConstantFieldsShouldNotBeVisible",
        Justification = "Add/remove is thread-safe for events in .NET.")]
    public static EventHandler<EventArgs> BackgroundTaskStarted;

    /// <summary>
    /// Event invoked when a background task completes.
    /// </summary>
    [SuppressMessage("Microsoft.Usage", "CA2211:NonConstantFieldsShouldNotBeVisible",
        Justification = "Add/remove is thread-safe for events in .NET.")]
    public static EventHandler<EventArgs> BackgroundTaskCompleted;

    /// <summary>
    /// Runs a task function on a background thread; invokes a completion action on the main thread.
    /// </summary>
    /// <typeparam name="T">Type of task function result.</typeparam>
    /// <param name="taskFunc">Task function to be run on a background thread.</param>
    /// <param name="completionAction">Completion action to run on the primary thread.</param>
    public static void RunBackgroundTask<T>(Func<T> taskFunc, Action<T> completionAction)
    {
        // Create a BackgroundWorker instance
        var backgroundWorker = new BackgroundWorker();

        // Attach to its DoWork event to run the task function and capture the result
        backgroundWorker.DoWork += delegate(object sender, DoWorkEventArgs e)
        {
            e.Result = taskFunc();
        };

        // Attach to its RunWorkerCompleted event to run the completion action
        backgroundWorker.RunWorkerCompleted += delegate(object sender, RunWorkerCompletedEventArgs e)
        {
            // Invoke the BackgroundTaskCompleted event
            var backgroundTaskFinishedHandler = BackgroundTaskCompleted;
            if (null != backgroundTaskFinishedHandler)
            {
                backgroundTaskFinishedHandler.Invoke(null, EventArgs.Empty);
            }

            // Call the completion action
            completionAction((T)e.Result);
        };

        // Invoke the BackgroundTaskStarted event
        var backgroundTaskStartedHandler = BackgroundTaskStarted;
        if (null != backgroundTaskStartedHandler)
        {
            backgroundTaskStartedHandler.Invoke(null, EventArgs.Empty);
        }

        // Run the BackgroundWorker asynchronously
        backgroundWorker.RunWorkerAsync();
    }
}

 

The sample application associated with this post lets you play around with synchronous and asynchronous operations of different durations. The red buttons on the left make synchronous calls that hang the entire application UI for the duration of the call (0, 2, or 10 seconds). (For fun, start a 10 second synchronous operation and then start clicking around the window - before long Windows will tag it as "Not responding" and gray the entire window out to reinforce the fact!) The green buttons on the right make asynchronous calls and leave the rest of the application completely responsive. You can queue up as many or as few simultaneous asynchronous calls as you'd like - they all run in parallel and without conflict. The status display shows the current number of pending asynchronous operations and helps to communicate what's going on in the background.

[Click here to download the source code for BackgroundTaskManager and the WPF/Silverlight sample applications shown above.]

One other thing that's worth pointing out is that while I originally wrote this code exclusively for use with WPF, it runs fine without changes on Silverlight as well (which you can see in the second screenshot above). In fact, the entire sample application is built from the exact same source code and XAML for both WPF and Silverlight (thanks to file linking)!

 

Whatever platform you're on, if your application has things to do and those things take time, please do what you can to keep your application responsive by doing that work off the UI thread. BackgroundTaskManager makes this easy, but your users will appreciate your efforts no matter how you solve the problem! :)