Switching From a BackgroundWorker To a Task - It's a Neater Solution, Particularly When Reporting Progress





5.00/5 (10 votes)
Why switch from the "traditional" BackgroundWorker to a Task? The old way works OK, but the new way generates "tidier" code which keeps stuff together (and needs less code)
Introduction
BackgroundWorker is fine; they work; they report progress to the UI thread via an event; they allow both a "percentage complete" value and a user defined object
to be handed up to the progress report handler method. But ... they take work to set up, and aren't strongly typed which raises the possibility of runtime errors that can't be caught at compile time.
Task also provides progress reporting via an event, but this is strongly typed via the IProgress<T> Interface which means you can't pass the wrong type of data to the handler. The code is also a lot cleaner and more obvious.
BackgroundWorker Code
private void MyButton_Click(object sender, EventArgs e)
{
showProgress.Show();
BackgroundWorker work = new BackgroundWorker();
work.WorkerReportsProgress = true;
work.ProgressChanged += Work_ProgressChanged;
work.RunWorkerCompleted += Work_RunWorkerCompleted;
work.DoWork += Work_DoWork;
work.RunWorkerAsync();
}
private void Work_DoWork(object sender, DoWorkEventArgs e)
{
if (sender is BackgroundWorker work)
{
for (int i = 0; i != 100; ++i)
{
work.ReportProgress(i);
Thread.Sleep(100);
}
}
}
private void Work_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
showProgress.Hide();
}
private void Work_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
showProgress.Value = e.ProgressPercentage;
if (e.UserState is DataTable dt)
{
DoSomething(dt);
}
}
Yes, you could use inline handlers, but ... no: because of the lack of strong typing, you get clumsy code anyway.
Task Code
private async void MyOtherButton_Click(object sender, EventArgs e)
{
showProgress.Show();
IProgress<int> progress = new Progress<int>(value => showProgress.Value = value);
await Task.Run(() =>
{
for (int i = 0; i != 100; ++i)
{
progress.Report(i);
Thread.Sleep(100);
}
});
showProgress.Hide();
}
Or:
private async void OrMyOtherButton_Click(object sender, EventArgs e)
{
showProgress.Show();
IProgress<DataTable> progress = new Progress<DataTable>(dt => DoSomething(dt));
await Task.Run(() =>
{
for (int i = 0; i != 100; ++i)
{
progress.Report(new DataTable());
Thread.Sleep(100);
}
});
showProgress.Hide();
}
Note that your handler method now needs the async
keyword.
To me, that's way more readable, and more obvious when you show and hide the progress bar.
Obviously, if you need to pass multiple objects to your progress handler, the usual suspects are available: a home grown class
or struct
, a KeyValuePair
, a tuple
... pick your favourite!
And ... Task
is more flexible: you can use Task.AwaitAll to wait for a whole bunch of tasks to complete a lot more easily than you can do the same with a BackGroundWorker
!
History
- 17th January, 2024: First version