Async/await vs BackgroundWorker
C#Task Parallel-LibraryBackgroundworker.Net 4.5Async AwaitC# Problem Overview
In the past few days I have tested the new features of .net 4.5 and c# 5.
I like its new async/await features. Earlier I had used BackgroundWorker to handle longer processes in the background with responsive UI.
My question is: after having these nice new features, when should I use async/await and when a BackgroundWorker? Which are the common scenarios for both?
C# Solutions
Solution 1 - C#
This is likely TL;DR for many, but, I think comparing await
with BackgroundWorker
is like comparing apples and oranges and my thoughts on this follow:
BackgroundWorker
is meant to model a single task that you'd want to perform in the background, on a thread pool thread. async
/await
is a syntax for asynchronously awaiting on asynchronous operations. Those operations may or may not use a thread pool thread or even use any other thread. So, they're apples and oranges.
For example, you can do something like the following with await
:
using (WebResponse response = await webReq.GetResponseAsync())
{
using (Stream responseStream = response.GetResponseStream())
{
int bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length);
}
}
But, you'd likely never model that in a background worker, you'd likely do something like this in .NET 4.0 (prior to await
):
webReq.BeginGetResponse(ar =>
{
WebResponse response = webReq.EndGetResponse(ar);
Stream responseStream = response.GetResponseStream();
responseStream.BeginRead(buffer, 0, buffer.Length, ar2 =>
{
int bytesRead = responseStream.EndRead(ar2);
responseStream.Dispose();
((IDisposable) response).Dispose();
}, null);
}, null);
Notice the disjointness of the disposal compared between the two syntaxes and how you can't use using
without async
/await
.
But, you wouldn't do something like that with BackgroundWorker
. BackgroundWorker
is usually for modeling a single long-running operation that you don't want to impact the UI responsiveness. For example:
worker.DoWork += (sender, e) =>
{
int i = 0;
// simulate lengthy operation
Stopwatch sw = Stopwatch.StartNew();
while (sw.Elapsed.TotalSeconds < 1)
++i;
};
worker.RunWorkerCompleted += (sender, eventArgs) =>
{
// TODO: do something on the UI thread, like
// update status or display "result"
};
worker.RunWorkerAsync();
There's really nothing there you can use async/await with, BackgroundWorker
is creating the thread for you.
Now, you could use TPL instead:
var synchronizationContext = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
int i = 0;
// simulate lengthy operation
Stopwatch sw = Stopwatch.StartNew();
while (sw.Elapsed.TotalSeconds < 1)
++i;
}).ContinueWith(t=>
{
// TODO: do something on the UI thread, like
// update status or display "result"
}, synchronizationContext);
In which case the TaskScheduler
is creating the thread for you (assuming the default TaskScheduler
), and could use await
as follows:
await Task.Factory.StartNew(() =>
{
int i = 0;
// simulate lengthy operation
Stopwatch sw = Stopwatch.StartNew();
while (sw.Elapsed.TotalSeconds < 1)
++i;
});
// TODO: do something on the UI thread, like
// update status or display "result"
In my opinion, a major comparison is whether you're reporting progress or not. For example, you might have a BackgroundWorker like
this:
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.ProgressChanged += (sender, eventArgs) =>
{
// TODO: something with progress, like update progress bar
};
worker.DoWork += (sender, e) =>
{
int i = 0;
// simulate lengthy operation
Stopwatch sw = Stopwatch.StartNew();
while (sw.Elapsed.TotalSeconds < 1)
{
if ((sw.Elapsed.TotalMilliseconds%100) == 0)
((BackgroundWorker)sender).ReportProgress((int) (1000 / sw.ElapsedMilliseconds));
++i;
}
};
worker.RunWorkerCompleted += (sender, eventArgs) =>
{
// do something on the UI thread, like
// update status or display "result"
};
worker.RunWorkerAsync();
But, you wouldn't deal with some of this because you'd drag-and-drop the background worker component on to the design surface of a form--something you can't do with async
/await
and Task
... i.e. you won't manually create the object, set the properties and set the event handlers. you'd only fill in the body of the DoWork
, RunWorkerCompleted
, and ProgressChanged
event handlers.
If you "converted" that to async/await, you'd do something like:
IProgress<int> progress = new Progress<int>();
progress.ProgressChanged += ( s, e ) =>
{
// TODO: do something with e.ProgressPercentage
// like update progress bar
};
await Task.Factory.StartNew(() =>
{
int i = 0;
// simulate lengthy operation
Stopwatch sw = Stopwatch.StartNew();
while (sw.Elapsed.TotalSeconds < 1)
{
if ((sw.Elapsed.TotalMilliseconds%100) == 0)
{
progress.Report((int) (1000 / sw.ElapsedMilliseconds))
}
++i;
}
});
// TODO: do something on the UI thread, like
// update status or display "result"
Without the ability to drag a component on to a Designer surface, it's really up to the reader to decide which is "better". But, that, to me, is the comparison between await
and BackgroundWorker
, not whether you can await built-in methods like Stream.ReadAsync
. e.g. if you were using BackgroundWorker
as intended, it could be hard to convert to use await
.
Other thoughts: http://jeremybytes.blogspot.ca/2012/05/backgroundworker-component-im-not-dead.html
Solution 2 - C#
async/await is designed to replace constructs such as the BackgroundWorker
. While you certainly can use it if you want to, you should be able to use async/await, along with a few other TPL tools, to handle everything that's out there.
Since both work, it comes down to personal preference as to which you use when. What is quicker for you? What is easier for you to understand?
Solution 3 - C#
This is a good introduction: http://msdn.microsoft.com/en-us/library/hh191443.aspx The Threads section is just what you are looking for:
> Async methods are intended to be non-blocking operations. An await expression in an async method doesn’t block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method. > > The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active. You can use Task.Run to move CPU-bound work to a background thread, but a background thread doesn't help with a process that's just waiting for results to become available. > > The async-based approach to asynchronous programming is preferable to existing approaches in almost every case. In particular, this approach is better than BackgroundWorker for IO-bound operations because the code is simpler and you don't have to guard against race conditions. In combination with Task.Run, async programming is better than BackgroundWorker for CPU-bound operations because async programming separates the coordination details of running your code from the work that Task.Run transfers to the threadpool.
Solution 4 - C#
BackgroundWorker is explicitly labeled as obsolete in .NET 4.5:
- in the book By Joseph Albahari, Ben Albahari "C# 5.0 in a Nutshell: The Definitive Reference"
- Stephen Cleary's answer to my question "Wasn't it .NET 4.0 TPL that made APM, EAP and BackgroundWorker asynchronous patterns obsolete?"
MSDN article "Asynchronous Programming with Async and Await (C# and Visual Basic)" tells:
> The async-based approach to asynchronous programming is preferable > to existing approaches in almost every case. In particular, this > approach is better than BackgroundWorker for IO-bound operations > because the code is simpler and you don't have to guard against race > conditions. In combination with Task.Run, async programming is better > than BackgroundWorker for CPU-bound operations because async > programming separates the coordination details of running your code > from the work that Task.Run transfers to the threadpool
UPDATE
- in response to @eran-otzap's comment:
"for IO-bound operations because the code is simpler and you don't have to guard against race conditions" What race conditions can occure , could you give an example ? "
This question should have been put as a separate post.
Wikipedia has a good explanation of racing conditions. The necessary part of it is multithreading and from the same MSDN article Asynchronous Programming with Async and Await (C# and Visual Basic):
> Async methods are intended to be non-blocking operations. An await > expression in an async method doesn’t block the current thread while > the awaited task is running. Instead, the expression signs up the rest > of the method as a continuation and returns control to the caller of > the async method. > > The async and await keywords don't cause additional threads to be > created. Async methods don't require multithreading because an async > method doesn't run on its own thread. The method runs on the current > synchronization context and uses time on the thread only when the > method is active. You can use Task.Run to move CPU-bound work to a > background thread, but a background thread doesn't help with a process > that's just waiting for results to become available. > > The async-based approach to asynchronous programming is preferable to > existing approaches in almost every case. In particular, this approach > is better than BackgroundWorker for IO-bound operations because the > code is simpler and you don't have to guard against race conditions. > In combination with Task.Run, async programming is better than > BackgroundWorker for CPU-bound operations because async programming > separates the coordination details of running your code from the work > that Task.Run transfers to the threadpool
That is, "The async and await keywords don't cause additional threads to be created".
As far as I can recall my own attempts when I was studying this article a year ago, if you have run and played with code sample from the same article, you could bump in situation that its non-async versions (you could try to convert it to yourself) block indefinitely!
Also, for concrete examples you could search this site. Here are some example:
Solution 5 - C#
Let's make an up-to-date comparison between a BackgroundWorker
and the Task.Run
+ Progress<T>
+ async/await combination. I will use both approaches to implement a simulated CPU-bound operation that must be offloaded to a background thread, in order to keep the UI responsive. The operation has a total duration of 5 seconds, and during the operation a ProgressBar
must be updated every 500 msec. Finally the result of the calculation must be displayed in a Label
. First the BackgroundWorker
implementation:
private void Button_Click(object sender, EventArgs e)
{
var worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += (object sender, DoWorkEventArgs e) =>
{
int sum = 0;
for (int i = 0; i < 100; i += 10)
{
worker.ReportProgress(i);
Thread.Sleep(500); // Simulate some time-consuming work
sum += i;
}
worker.ReportProgress(100);
e.Result = sum;
};
worker.ProgressChanged += (object sender, ProgressChangedEventArgs e) =>
{
ProgressBar1.Value = e.ProgressPercentage;
};
worker.RunWorkerCompleted += (object sender, RunWorkerCompletedEventArgs e) =>
{
int result = (int)e.Result;
Label1.Text = $"Result: {result:#,0}";
};
worker.RunWorkerAsync();
}
24 lines of code inside the event handler. Now let's do exactly the same with the modern approach:
private async void Button_Click(object sender, EventArgs e)
{
IProgress<int> progress = new Progress<int>(percent =>
{
ProgressBar1.Value = percent;
});
int result = await Task.Run(() =>
{
int sum = 0;
for (int i = 0; i < 100; i += 10)
{
progress.Report(i);
Thread.Sleep(500); // Simulate some time-consuming work
sum += i;
}
progress.Report(100);
return sum;
});
Label1.Text = $"Result: {result:#,0}";
}
17 lines of code inside the event handler. Quite less code overall.
In both cases the work is executed on a ThreadPool
thread.
Advantages of the BackgroundWorker
approach:
- Can be used with projects targeting the .NET Framework 4.0 and earlier.
Advantages of the Task.Run
+ Progress<T>
+ async
/await
approach:
- The result is strongly-typed. No need to cast from an
object
. No risk of a run-timeInvalidCastException
. - The continuation after the completion of the work is running in the original scope, not inside a lamda.
- Allows to report arbitrary strongly-type information through the
Progress
. On the contrary aBackgroundWorker
forces you to pass any extra information as anobject
, and then cast back from theobject
ProgressChangedEventArgs.UserState
property. - Allows to use multiple
Progress
objects, to report different progress data with different frequencies, easily. This is very tedious and error-prone with aBackgroundWorker
. - Canceling the operation follows the standard .NET pattern for cooperative cancellation: the
CancellationTokenSource
+CancellationToken
combo. There are currently thousands of .NET APIs that consume aCancellationToken
. On the contrary theBackgroundWorker
s cancellation mechanism cannot be consumed because it doesn't generate notifications. - Finally the
Task.Run
supports both synchronous and asynchronous workloads with the same ease. TheBackgroundWorker
can consume asynchronous APIs only by blocking the worker thread.