I've used BackgroundWorkers for a long time - most of my apps use them to free up the UI from long running tasks, and they have a built in progress reporting mechanism which a raw Thread lacks. But I've recently realized that the Task class can do it as well, and in a really tidy way. So this tip shows you the old and new ways, so you can easily switch.
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