Click here to Skip to main content
15,896,912 members
Articles / Programming Languages / C#
Tip/Trick

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

Rate me:
Please Sign up or sign in to vote.
5.00/5 (10 votes)
17 Jan 2024CPOL1 min read 11.1K   22   13
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)


Written By
CEO
Wales Wales
Born at an early age, he grew older. At the same time, his hair grew longer, and was tied up behind his head.
Has problems spelling the word "the".
Invented the portable cat-flap.
Currently, has not died yet. Or has he?

Comments and Discussions

 
QuestionTask & Async Await - In my opinion just more Syntactic Sugar. Pin
Russell Mangel8-Apr-24 2:38
Russell Mangel8-Apr-24 2:38 
Show me one thing that Task & Async await can do in the real world, and I'll start using it for that.

I tried for 2 weeks to get a Task / async / await to do one thing and it could not. That one thing is this:

Let's say you have 10 files that are 8GB each. Now write a .net 4.5 or later application to transfer those files in the background. Start the BackGround operation, then cancel the operation. Yeah, you can get it to cancel *AFTER* one of the files completes, but at 8GB each you will be waiting for minutes for the operation to cancel.

This is because you will be calling either File.Copy() or FileInfo.CopyTo() on a back ground thread. I was unable to cancel the file transfer in the middle of the copy. You can't do it with BackGroundWorker either, same problem.

However, I am hopeful that using the old tried true and trusted APM. I'll bet I can do it. So much for EAP, and TAP. Show me how to do it. Betcha can't without doing some esoteric Thread interruption routine which would destroy your chances of catching an exception there.

So then I decided to try just using Streams to copy the file, I built a buffer that was reasonable, and it does work you can dynamically adjust the Buffer size so you can cancel the operation, that works.

Yes here comes the *HOWEVER*, the performance of that is dismal, about a 40% reduction in file copy performance. I have no Idea why. I thought direct Streams were fast.

So I'm currently writing the APM code in .NET 2.0. Yeah way back.

That said, I do like the BackGround Worker it works and plays nice with Windows Forms, which is what they wrote it for.

Cheers.
Russell Mangel
Las Vegas, NV

AnswerRe: Task & Async Await - In my opinion just more Syntactic Sugar. Pin
OriginalGriff8-Apr-24 4:05
mveOriginalGriff8-Apr-24 4:05 
GeneralRe: Task & Async Await - In my opinion just more Syntactic Sugar. Pin
Russell Mangel8-Apr-24 4:54
Russell Mangel8-Apr-24 4:54 
GeneralRe: Task & Async Await - In my opinion just more Syntactic Sugar. Pin
Russell Mangel8-Apr-24 5:09
Russell Mangel8-Apr-24 5:09 
PraiseGreat little article Pin
Bruce Greene19-Jan-24 11:09
Bruce Greene19-Jan-24 11:09 
GeneralRe: Great little article Pin
OriginalGriff19-Jan-24 23:24
mveOriginalGriff19-Jan-24 23:24 
PraisePortable cat-flap Pin
Terry Hutt18-Jan-24 11:43
Terry Hutt18-Jan-24 11:43 
GeneralRe: Portable cat-flap Pin
OriginalGriff18-Jan-24 19:33
mveOriginalGriff18-Jan-24 19:33 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA18-Jan-24 3:56
professionalȘtefan-Mihai MOGA18-Jan-24 3:56 
QuestionSome notes Pin
Graeme_Grant17-Jan-24 6:32
mvaGraeme_Grant17-Jan-24 6:32 
AnswerRe: Some notes Pin
OriginalGriff17-Jan-24 11:44
mveOriginalGriff17-Jan-24 11:44 
GeneralRe: Some notes Pin
Graeme_Grant17-Jan-24 12:05
mvaGraeme_Grant17-Jan-24 12:05 
QuestionGot my 5 Pin
Mike Hankey17-Jan-24 4:01
mveMike Hankey17-Jan-24 4:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.