ChatGPT解决这个技术问题 Extra ChatGPT

In the past few days I have tested the new features of .net 4.5 and c# 5.

I like its new async/await features. Earlier I had used BackgroundWorker to handle longer processes in the background with responsive UI.

My question is: after having these nice new features, when should I use async/await and when a BackgroundWorker? Which are the common scenarios for both?

Both are good, but if your working with older code that has not be migrated to a later .net version; BackgroundWorker works on both.

P
Peter Ritchie

This is likely TL;DR for many, but, I think comparing await with BackgroundWorker is like comparing apples and oranges and my thoughts on this follow:

BackgroundWorker is meant to model a single task that you'd want to perform in the background, on a thread pool thread. async/await is a syntax for asynchronously awaiting on asynchronous operations. Those operations may or may not use a thread pool thread or even use any other thread. So, they're apples and oranges.

For example, you can do something like the following with await:

using (WebResponse response = await webReq.GetResponseAsync())
{
    using (Stream responseStream = response.GetResponseStream())
    {
        int bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length);
    }
}

But, you'd likely never model that in a background worker, you'd likely do something like this in .NET 4.0 (prior to await):

webReq.BeginGetResponse(ar =>
{
    WebResponse response = webReq.EndGetResponse(ar);
    Stream responseStream = response.GetResponseStream();
    responseStream.BeginRead(buffer, 0, buffer.Length, ar2 =>
    {
        int bytesRead = responseStream.EndRead(ar2);
        responseStream.Dispose();
        ((IDisposable) response).Dispose();
    }, null);
}, null);

Notice the disjointness of the disposal compared between the two syntaxes and how you can't use using without async/await.

But, you wouldn't do something like that with BackgroundWorker. BackgroundWorker is usually for modeling a single long-running operation that you don't want to impact the UI responsiveness. For example:

worker.DoWork += (sender, e) =>
                    {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                        ++i;
                    };
worker.RunWorkerCompleted += (sender, eventArgs) =>
                                {
                                    // TODO: do something on the UI thread, like
                                    // update status or display "result"
                                };
worker.RunWorkerAsync();

There's really nothing there you can use async/await with, BackgroundWorker is creating the thread for you.

Now, you could use TPL instead:

var synchronizationContext = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
                      {
                        int i = 0;
                        // simulate lengthy operation
                        Stopwatch sw = Stopwatch.StartNew();
                        while (sw.Elapsed.TotalSeconds < 1)
                            ++i;
                      }).ContinueWith(t=>
                                      {
                                        // TODO: do something on the UI thread, like
                                        // update status or display "result"
                                      }, synchronizationContext);

In which case the TaskScheduler is creating the thread for you (assuming the default TaskScheduler), and could use await as follows:

await Task.Factory.StartNew(() =>
                  {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                        ++i;
                  });
// TODO: do something on the UI thread, like
// update status or display "result"

In my opinion, a major comparison is whether you're reporting progress or not. For example, you might have a BackgroundWorker like this:

BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.ProgressChanged += (sender, eventArgs) =>
                            {
                            // TODO: something with progress, like update progress bar

                            };
worker.DoWork += (sender, e) =>
                 {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                    {
                        if ((sw.Elapsed.TotalMilliseconds%100) == 0)
                            ((BackgroundWorker)sender).ReportProgress((int) (1000 / sw.ElapsedMilliseconds));
                        ++i;
                    }
                 };
worker.RunWorkerCompleted += (sender, eventArgs) =>
                                {
                                    // do something on the UI thread, like
                                    // update status or display "result"
                                };
worker.RunWorkerAsync();

But, you wouldn't deal with some of this because you'd drag-and-drop the background worker component on to the design surface of a form--something you can't do with async/await and Task... i.e. you won't manually create the object, set the properties and set the event handlers. you'd only fill in the body of the DoWork, RunWorkerCompleted, and ProgressChanged event handlers.

If you "converted" that to async/await, you'd do something like:

     IProgress<int> progress = new Progress<int>();

     progress.ProgressChanged += ( s, e ) =>
        {
           // TODO: do something with e.ProgressPercentage
           // like update progress bar
        };

     await Task.Factory.StartNew(() =>
                  {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                    {
                        if ((sw.Elapsed.TotalMilliseconds%100) == 0)
                        {
                            progress.Report((int) (1000 / sw.ElapsedMilliseconds))
                        }
                        ++i;
                    }
                  });
// TODO: do something on the UI thread, like
// update status or display "result"

Without the ability to drag a component on to a Designer surface, it's really up to the reader to decide which is "better". But, that, to me, is the comparison between await and BackgroundWorker, not whether you can await built-in methods like Stream.ReadAsync. e.g. if you were using BackgroundWorker as intended, it could be hard to convert to use await.

Other thoughts: http://jeremybytes.blogspot.ca/2012/05/backgroundworker-component-im-not-dead.html


One flaw I think exists with async/await is that you may want to start multiple async tasks at once. await is meant to wait for each task to complete before starting the next one. And if you omit the await keyword then the method runs synchronously which is not what you want. I don't think async/await can solve a problem such as "start these 5 tasks and call me back when each task is done in no particular order".
@Moozhe. Not true, you can do var t1 = webReq.GetResponseAsync(); var t2 = webReq2.GetResponseAsync(); await t1; await t2;. Which would await two parallel operations. Await is much better for asynchronous, but sequential tasks, IMO...
@Moozhe yes, doing it that way maintains a certain sequence--as I mentioned. this is the main point of await is to get the asynchronisity in sequential-looking code. You could, of course, use await Task.WhenAny(t1, t2) to do something when the either task completed first. You'd likely want a loop to make sure the other task completes too. Usually you want to know when a specific task completes, which leads you to writing sequential awaits.
Honesty, BackgroundWorker was never good for IO-bound operations.
S
Servy

async/await is designed to replace constructs such as the BackgroundWorker. While you certainly can use it if you want to, you should be able to use async/await, along with a few other TPL tools, to handle everything that's out there.

Since both work, it comes down to personal preference as to which you use when. What is quicker for you? What is easier for you to understand?


Thanks. For me async/await seems much more clear and 'natural'. BakcgoundWorker makes the code 'noisy' in my opinion.
@Tom Well, that's why Microsoft spent a lot of time and effort implementing it. If it wasn't any better they wouldn't have bothered
Yes. The new await stuff makes the old BackgroundWorker appear to be totally inferior and obsolete. The difference is that dramatic.
I've got a pretty good rundown on my blog comparing different approaches to background tasks. Note that async / await also allows asynchronous programming without thread pool threads.
Downvoted this answer, it's misleading. Async/await is NOT designed to replace background worker.
C
Community

This is a good introduction: http://msdn.microsoft.com/en-us/library/hh191443.aspx The Threads section is just what you are looking for:

Async methods are intended to be non-blocking operations. An await expression in an async method doesn’t block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method. The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active. You can use Task.Run to move CPU-bound work to a background thread, but a background thread doesn't help with a process that's just waiting for results to become available. The async-based approach to asynchronous programming is preferable to existing approaches in almost every case. In particular, this approach is better than BackgroundWorker for IO-bound operations because the code is simpler and you don't have to guard against race conditions. In combination with Task.Run, async programming is better than BackgroundWorker for CPU-bound operations because async programming separates the coordination details of running your code from the work that Task.Run transfers to the threadpool.


"for IO-bound operations because the code is simpler and you don't have to guard against race conditions" What race conditions can occure , could you give an example ?
C
Community

BackgroundWorker is explicitly labeled as obsolete in .NET 4.5:

in the book By Joseph Albahari, Ben Albahari "C# 5.0 in a Nutshell: The Definitive Reference"

Stephen Cleary's answer to my question "Wasn't it .NET 4.0 TPL that made APM, EAP and BackgroundWorker asynchronous patterns obsolete?"

MSDN article "Asynchronous Programming with Async and Await (C# and Visual Basic)" tells:

The async-based approach to asynchronous programming is preferable to existing approaches in almost every case. In particular, this approach is better than BackgroundWorker for IO-bound operations because the code is simpler and you don't have to guard against race conditions. In combination with Task.Run, async programming is better than BackgroundWorker for CPU-bound operations because async programming separates the coordination details of running your code from the work that Task.Run transfers to the threadpool

UPDATE

in response to @eran-otzap's comment: "for IO-bound operations because the code is simpler and you don't have to guard against race conditions" What race conditions can occure , could you give an example ? "

This question should have been put as a separate post.

Wikipedia has a good explanation of racing conditions. The necessary part of it is multithreading and from the same MSDN article Asynchronous Programming with Async and Await (C# and Visual Basic):

Async methods are intended to be non-blocking operations. An await expression in an async method doesn’t block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method. The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active. You can use Task.Run to move CPU-bound work to a background thread, but a background thread doesn't help with a process that's just waiting for results to become available. The async-based approach to asynchronous programming is preferable to existing approaches in almost every case. In particular, this approach is better than BackgroundWorker for IO-bound operations because the code is simpler and you don't have to guard against race conditions. In combination with Task.Run, async programming is better than BackgroundWorker for CPU-bound operations because async programming separates the coordination details of running your code from the work that Task.Run transfers to the threadpool

That is, "The async and await keywords don't cause additional threads to be created".

As far as I can recall my own attempts when I was studying this article a year ago, if you have run and played with code sample from the same article, you could bump in situation that its non-async versions (you could try to convert it to yourself) block indefinitely!

Also, for concrete examples you could search this site. Here are some example:

Calling an async method with c#5.0

Why does this code fail when executed via TPL/Tasks?

await vs Task.Wait - Deadlock?


BackgrondWorker is not explicitly labeled as obsolete in .NET 4.5. The MSDN article merely says that IO-bound operations are better with async methods--use of BackgroundWorker doesn't mean you can't use async methods.
@PeterRitchie , I corrected my answer. For me, "existing approaches are obsolete" is synonym to "The async-based approach to asynchronous programming is preferable to existing approaches in almost every case"
I take issue with that MSDN page. For one, you do no more "coordination" with BGW than you do with Task. And, yes BGW was never intended to directly perform IO operstions--there'd always been a better way to do IO than in BGW. The other answer shows BGW is no more complex to use than Task. And if you use BGW correctly, there are no race conditions.
"for IO-bound operations because the code is simpler and you don't have to guard against race conditions" What race conditions can occure , could you give an example ?
This answer is wrong. Asynchronous programming can too easily trigger deadlocks in non-trivial programs. In comparison BackgroundWorker is simple and rock solid.
T
Theodor Zoulias

Let's make an up-to-date comparison between a BackgroundWorker and the Task.Run + Progress<T> + async/await combination. I will use both approaches to implement a simulated CPU-bound operation that must be offloaded to a background thread, in order to keep the UI responsive. The operation has a total duration of 5 seconds, and during the operation a ProgressBar must be updated every 500 msec. Finally the result of the calculation must be displayed in a Label. First the BackgroundWorker implementation:

private void Button_Click(object sender, EventArgs e)
{
    var worker = new BackgroundWorker();
    worker.WorkerReportsProgress = true;
    worker.DoWork += (object sender, DoWorkEventArgs e) =>
    {
        int sum = 0;
        for (int i = 0; i < 100; i += 10)
        {
            worker.ReportProgress(i);
            Thread.Sleep(500); // Simulate some time-consuming work
            sum += i;
        }
        worker.ReportProgress(100);
        e.Result = sum;
    };
    worker.ProgressChanged += (object sender, ProgressChangedEventArgs e) =>
    {
        ProgressBar1.Value = e.ProgressPercentage;
    };
    worker.RunWorkerCompleted += (object sender, RunWorkerCompletedEventArgs e) =>
    {
        int result = (int)e.Result;
        Label1.Text = $"Result: {result:#,0}";
    };
    worker.RunWorkerAsync();
}

24 lines of code inside the event handler. Now let's do exactly the same with the modern approach:

private async void Button_Click(object sender, EventArgs e)
{
    IProgress<int> progress = new Progress<int>(percent =>
    {
        ProgressBar1.Value = percent;
    });
    int result = await Task.Run(() =>
    {
        int sum = 0;
        for (int i = 0; i < 100; i += 10)
        {
            progress.Report(i);
            Thread.Sleep(500); // Simulate some time-consuming work
            sum += i;
        }
        progress.Report(100);
        return sum;
    });
    Label1.Text = $"Result: {result:#,0}";
}

17 lines of code inside the event handler. Quite less code overall.

In both cases the work is executed on a ThreadPool thread.

Advantages of the BackgroundWorker approach:

Can be used with projects targeting the .NET Framework 4.0 and earlier.

Advantages of the Task.Run + Progress<T> + async/await approach:

The result is strongly-typed. No need to cast from an object. No risk of a run-time InvalidCastException. The continuation after the completion of the work is running in the original scope, not inside a lamda. Allows to report arbitrary strongly-type information through the Progress. On the contrary a BackgroundWorker forces you to pass any extra information as an object, and then cast back from the object ProgressChangedEventArgs.UserState property. Allows to use multiple Progress objects, to report different progress data with different frequencies, easily. This is very tedious and error-prone with a BackgroundWorker. Canceling the operation follows the standard .NET pattern for cooperative cancellation: the CancellationTokenSource + CancellationToken combo. There are currently thousands of .NET APIs that consume a CancellationToken. On the contrary the BackgroundWorkers cancellation mechanism cannot be consumed because it doesn't generate notifications. Finally the Task.Run supports both synchronous and asynchronous workloads with the same ease. The BackgroundWorker can consume asynchronous APIs only by blocking the worker thread.


Btw recently I became aware that Stephen Cleary has posted a series of blog posts on the same theme.