Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

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

5.00/5 (10 votes)
17 Jan 2024CPOL1 min read 13K  
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)
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

C#
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

C#
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:

C#
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)