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:
- TPL Async (Ian Griffiths)
- Intro to Async and Parallel Programming in .NET 4 (Joe Hummel)
- Async and Parallel Programming: Application Design (Joe Hummel)
Async and Await
No comments:
Post a Comment