Task.Run vs BackgroundWorker: Conclusion
• CommentsIn this series on Task.Run vs BackgroundWorker, we’ve looked at the most common aspects of running background tasks. As a recap, here’s the full list of posts in chronological order:
- Introduction - we’re only contrasting
Task.Run
withBackgroundWorker
for situations thatBackgroundWorker
was designed for. - Round 1: Basics - how to run code on a background thread and receive a completion notification marshaled back to the UI thread. The
Task.Run
code is shorter and simpler with less “ceremony code”. - Round 2: Errors - how to handle exceptions from the background thread code. The
Task.Run
code uses the more natural and less error-pronetry/catch
blocks, and has less error-prone exception propagation. - Round 3: Results - how to retrieve a result value from the background thread. The
Task.Run
code uses the more naturalreturn
statement and the result value is strongly-typed. - Round 4: Cancellation - how to cancel the background thread. The
Task.Run
code uses the common cancellation framework, which is simpler, less error-prone, and interoperates more cleanly with other cancellation-aware APIs. - Round 5: Progress Reports - how to support progress updates from the background thread. The
Task.Run
code uses a strongly-typed progress report type.
What I am not planning to cover in this series are more complex situations, which is actually where Task.Run
really outperforms BackgroundWorker
. For example, nesting one background operation within another is easier with Task.Run
. Also, anything like waiting for two separate background operations to complete before doing something else is much easier with Task.Run
. Pretty much any time you have to coordinate background operations, Task.Run
code is going to be much simpler!
I hope that this series is sufficient to convince you that BackgroundWorker
is a type that should not be used in new code. Everything it can do, Task.Run
can do better; and Task.Run
can do a lot of things that BackgroundWorker
can’t!
I’ll leave you with a “combined” example. The code below starts a cancelable background operation that reports progress, and will either throw an exception or return a value. These are all the basic operations of BackgroundWorker
. One of these uses BackgroundWorker
and the other uses Task.Run
. Don’t just look at the length of the code; consider all the little nuances of how it works (type safety, how easily the API can be misused, etc). Then ask yourself: which code would I rather maintain?
private BackgroundWorker _bgw;
private void button1_Click(object sender, EventArgs e)
{
var fail = checkBox1.Checked;
_bgw = new BackgroundWorker();
var bgw = _bgw;
bgw.WorkerSupportsCancellation = true;
bgw.WorkerReportsProgress = true;
bgw.DoWork += (_, args) =>
{
for (int i = 0; i != 100; ++i)
{
bgw.ReportProgress(0, i + "%");
if (bgw.CancellationPending)
{
args.Cancel = true;
return;
}
Thread.Sleep(100);
}
if (fail)
throw new InvalidOperationException("Requested to fail.");
args.Result = 13;
};
bgw.ProgressChanged += (_, args) =>
{
label1.Text = (string)args.UserState;
};
bgw.RunWorkerCompleted += (_, args) =>
{
if (args.Cancelled)
{
label1.Text = "Cancelled.";
}
else if (args.Error == null)
{
var result = (int)args.Result;
label1.Text = "Completed: " + result;
}
else
{
label1.Text = args.Error.GetType().Name + ": " + args.Error.Message;
}
};
bgw.RunWorkerAsync();
}
private void cancelButton1_Click(object sender, EventArgs e)
{
if (_bgw != null)
_bgw.CancelAsync();
}
private CancellationTokenSource _cts;
private async void button2_Click(object sender, EventArgs e)
{
var fail = checkBox1.Checked;
_cts = new CancellationTokenSource();
var token = _cts.Token;
var progressHandler = new Progress<string>(value =>
{
label2.Text = value;
});
var progress = progressHandler as IProgress<string>;
try
{
var result = await Task.Run(() =>
{
for (int i = 0; i != 100; ++i)
{
if (progress != null)
progress.Report(i + "%");
token.ThrowIfCancellationRequested();
Thread.Sleep(100);
}
if (fail)
throw new InvalidOperationException("Requested to fail.");
return 13;
});
label2.Text = "Completed: " + result;
}
catch (OperationCanceledException)
{
label2.Text = "Cancelled.";
}
catch (Exception ex)
{
label2.Text = ex.GetType().Name + ": " + ex.Message;
}
}
private void cancelButton2_Click(object sender, EventArgs e)
{
if (_cts != null)
_cts.Cancel();
}