Thursday, 10 July 2014

Six ways to initiate tasks on another thread in .NET

Over the years, Microsoft have provided us with multiple different ways to kick off tasks on a background thread in .NET. It can be rather bewildering to decide which one you ought to use. So here’s my very quick guide to the choices available. We’ll start with the ones that have been around the longest, and move on to the newer options.

Asynchronous Delegates

Since the beginning of .NET you have been able to take any delegate and call BeginInvoke on it to run that method asynchronously. If you don’t care about when it ends, it’s actually quite simple to do. Here we’ll call a method that takes a string:

Action<string> d = BackgroundTask;
d.BeginInvoke("BeginInvoke", null, null);

The BeginInvoke method also takes a callback parameter and some optional state. This allows us to get notification when the background task has completed. We call EndInvoke to get any return value and also to catch any exception thrown in our function. BeginInvoke also returns an IAsyncResult allowing us to check or wait for completion.

This model is called the Asynchronous Programming Model (APM). It is quite powerful, but is a fairly cumbersome programming model, and not particularly great if you want to chain asynchronous methods together as you end up with convoluted flow control over lots of callbacks, and find yourself needing to pass state around in awkward ways.

Thread Class

Another option that has been in .NET since the beginning is the Thread class. You can create a new Thread object, set up various properties such as the method to execute, thread name, and priority, and then start the thread.

var t = new Thread(BackgroundTask);
t.Name = "My Thread";
t.Priority = ThreadPriority.AboveNormal;
t.Start("Thread");
This may seem like the obvious choice if you need to run something on another thread, but it is actually overkill for most scenarios.

It’s better to let .NET manage the creation of threads rather than spinning them up yourself. I only tend to use this approach if I need a dedicated thread for a single task that is running for the lifetime of my application.

ThreadPool

The ThreadPool was introduced fairly early on in .NET (v1.1 I think) and provided an extremely simple way to request that your method be run on a thread from the thread pool. You just call QueueUserWorkItem and pass in your method and state. If there are free threads it will start immediately, otherwise it will be queued up. The disadvantage of this approach compared to the previous two is that it provides no mechanism for notification of when your task has finished. It’s up to you to report completion and catch exceptions.

ThreadPool.QueueUserWorkItem(BackgroundTask, "ThreadPool");

This technique has now really been superseded by the Task Parallel Library (see below), which does everything the ThreadPool class can do and much more. If you’re still using it, its time to learn TPL.

BackgroundWorker Component

The BackgroundWorker component was introduced in .NET 2 and is designed to make it really easy for developers to run a task on a background thread. It covers all the basics of reporting progress, cancellation, catching exceptions, and getting you back onto the UI thread so you can update the user interface.

You put the code for your background thread into the DoWork event handler of the background worker:

backgroundWorker1.DoWork += BackgroundWorker1OnDoWork;

and within that function you are able to report progress:

backgroundWorker1.ReportProgress(30, progressMessage);

You can also check if the user has requested cancellation with the BackgroundWorker.CancellationPending flag.

The BackgroundWorker provides a RunWorkerCompleted event that you can subscribe to and get hold of the results of the background task. It makes it easy to determine if you finished successfully, were cancelled, or if an exception that was thrown. The RunWorkerCompleted and ProgressChanged events will both fire on the UI thread, eliminating any need for you to get back onto that thread yourself.

BackgroundWorker is a great choice if you are using WinForms or WPF. My only one criticism of it is that it does tend to encourage people to put business logic into the code behind of their UI. But you don't have to use BackgroundWorker like that. You can create one in your ViewModel if you are doing MVVM, or you could make your DoWork event handler simply call directly into a business object.

Task Parallel Library (TPL)

The Task Parallel Library was introduced in .NET 4 as the new preferred way to initiate background tasks. It is a powerful model, supporting chaining tasks together, executing them in parallel, waiting on one or many tasks to complete, passing cancellation tokens around, and even controlling what thread they will run on.

In its simplest form, you can kick off a background task with TPL in much the same way that you kicked off a thread with the ThreadPool.

Task.Run(() => BackgroundTask("TPL"));

Unlike the ThreadPool though, we get back a Task object, allowing you to wait for completion, or specify another task to be run when this one completes. The TPL is extremely powerful, but there is a lot to learn, so make sure you check out the resources below for learning more.

C# 5 async await

The async and await keywords were introduced with C# 5 and .NET 4.5 and allow you to write synchronous looking code that actually runs asynchronously. It’s not actually an alternative to the TPL; it augments it and provides an easier programming model. You can call await on any method that returns a task, or if you need to call an existing synchronous method you can do that by using the TPL to turn it into a task:

await Task.Run(() => xdoc.Load("http://feeds.feedburner.com/soundcode"));

The  advantages are that this produces much easier to read code, and another really nice touch is that when you are on a UI thread and await a method, when control resumes you will be back on the UI thread again:

await Task.Run(() => xdoc.Load("http://feeds.feedburner.com/soundcode"));
label1.Text = "Done"; // we’re back on the UI thread!

This model also allows you to put one try…catch block around code that is running on multiple threads, which is not possible with the other models discussed. It is also now possible to use async and await with .NET 4 using the BCL Async Nuget Package.

Here’s a slightly longer example showing a button click event handler in a Windows Forms application that calls a couple of awaitable tasks, catches exceptions whatever thread its on and updates the GUI along the way:

private async void OnButtonAsyncAwaitClick(object sender, EventArgs e)
{
    const string state = "Async Await";
    this.Cursor = Cursors.WaitCursor;
    try
    {
        label1.Text = String.Format("{0} Started", state);
        await AwaitableBackgroundTask(state);
        label1.Text = String.Format("About to load XML"); 
        var xdoc = new XmlDocument();
        await Task.Run(() => xdoc.Load("http://feeds.feedburner.com/soundcode"));
        label1.Text = String.Format("{0} Done {1}", state, xdoc.FirstChild.Name);
    }
    catch (Exception ex)
    {
        label1.Text = ex.Message;
    }
    finally
    {
        this.Cursor = Cursors.Default;
    }
}

Learn More

Obviously I can’t fully explain how to use each of these approaches in a short blog post like this, but many of these programming models are covered in depth by some of my fellow Pluralsight Authors. I’d strongly recommend picking at least one of these technologies to master (TPL with async await would be my suggestion).

Thread and ThreadPool:

Asynchronous Delegates:

BackgroundWorker:

Task Parallel Library:

Async and Await

No comments: