Async/await vs BackgroundWorker

Tom*_*Tom 154 c# backgroundworker task-parallel-library async-await .net-4.5

在过去的几天里,我测试了.net 4.5和c#5的新功能.

我喜欢它的新async/await功能.之前我曾使用BackgroundWorker通过响应式UI在后台处理更长的进程.

我的问题是:在拥有这些不错的新功能之后,我何时应该使用async/await和什么时候使用BackgroundWorker?两者的常见情况是什么?

Pet*_*hie 194

这可能是TL; DR为多,但是,我认为比较awaitBackgroundWorker就是喜欢上了这后续比较苹果和桔子和我的想法:

BackgroundWorker用于在线程池线程上为您要在后台执行的单个任务建模. async/ await是异步等待异步操作的语法.这些操作可能会也可能不会使用线程池线程甚至使用任何其他线程.所以,他们是苹果和橘子.

例如,您可以执行以下操作await:

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

但是,您可能永远不会在后台工作程序中对此进行建模,您可能会在.NET 4.0(之前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);
Run Code Online (Sandbox Code Playgroud)

请注意比较了两种语法,以及如何你不能使用的处置不相交usingasync/ await.

但是,你不会做那样的事情BackgroundWorker. BackgroundWorker通常用于建模您不希望影响UI响应的单个长时间运行操作.例如:

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();
Run Code Online (Sandbox Code Playgroud)

你真的没有什么可以使用async/await,BackgroundWorker为你创建线程.

现在,您可以使用TPL代替:

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);
Run Code Online (Sandbox Code Playgroud)

在这种情况下,TaskScheduler为您创建线程(假设默认值TaskScheduler),并可以使用await如下:

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"
Run Code Online (Sandbox Code Playgroud)

在我看来,一个重要的比较是你是否报告进展.例如,你可能有BackgroundWorker like这个:

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();
Run Code Online (Sandbox Code Playgroud)

但是,你不会处理一些这一点,因为你会拖和拖放到窗体的设计表面的背景工作者组成部分-这是你不能做async/ awaitTask...即你赢了" t手动创建对象,设置属性并设置事件处理程序.你只填写的身体DoWork,RunWorkerCompletedProgressChanged事件处理程序.

如果您将其"转换"为异步/等待,您可以执行以下操作:

     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"
Run Code Online (Sandbox Code Playgroud)

如果没有将组件拖到Designer表面的能力,那么由读者来决定哪个"更好".但是,对我来说,这是和之间的比较,awaitBackgroundWorker不是你是否可以等待内置方法Stream.ReadAsync.例如,如果您BackgroundWorker按预期使用,可能很难转换为使用await.

其他想法:http://jeremybytes.blogspot.ca/2012/05/backgroundworker-component-im-not-dead.html

  • 诚实,BackgroundWorker从来没有*对IO绑定操作有益. (5认同)
  • @Moozhe.不是这样,你可以做`var t1 = webReq.GetResponseAsync(); var t2 = webReq2.GetResponseAsync(); 等待t1; 等待t2;`.哪个会等待两个并行操作.对于异步但顺序的任务,IMO要好得多,IMO ...... (4认同)
  • 我认为async/await存在的一个缺陷是您可能希望一次启动多个异步任务.await意味着在开始下一个任务之前等待每个任务完成.如果省略await关键字,则该方法将同步运行,这不是您想要的.我不认为async/await可以解决诸如"启动这5个任务并在没有特定顺序完成每个任务时给我回电话"的问题. (2认同)
  • @Moozhe是的,这样做可以维持一定的顺序 - 正如我所提到的那样.这是等待的主要观点是在顺序代码中获得异步性.当然,当任务完成时,你可以使用`await Task.WhenAny(t1,t2)`来做某事.你可能想要一个循环来确保其他任务也完成.通常你想知道*特定*任务何时完成,这会导致你编写顺序`await`s. (2认同)
  • [是不是.NET 4.0 TPL让APM,EAP和BackgroundWorker异步模式过时了?](http://stackoverflow.com/questions/16363263/wasnt-it-net-4-0-tpl-that-made- apm-eap-and-backgroundworker-asynchronous-patt)仍然对此感到困惑 (2认同)

Ser*_*rvy 73

async/await旨在替换诸如BackgroundWorker.之类的结构.如果你愿意,你当然可以使用它,你应该能够使用async/await和其他一些TPL工具来处理那里的所有内容.

由于两者都起作用,因此归结为个人偏好,以及您何时使用.什么对更快?什么是更容易为理解吗?

  • 谢谢.对我来说async/await似乎更加清晰和"自然".在我看来,BakcgoundWorker使代码"嘈杂". (15认同)
  • [我的博客](http://nitoprograms.blogspot.com/2010/08/various-implementations-of-asynchronous.html)比较了不同的后台任务方法.请注意,`async` /`await`也允许异步编程*而不使用*线程池线程. (13认同)
  • @Tom嗯,这就是微软花费大量时间和精力实现它的原因.如果不是更好,他们就不会感到困扰 (11认同)
  • 低估了这个答案,这是误导.Async/await不是为了替换后台工作者而设计的. (8认同)
  • 是.新的等待东西使旧的BackgroundWorker看起来完全劣等和过时.不同之处在于戏剧性. (4认同)
  • @Quango当然是.它不仅仅是为了做那件事而设计的,但是正在开发的`async` /`await`的一个主要用例是协助BGW设计处理的确切情况,即执行一些工作异步创建UI应用程序,并在工作完成时更新UI. (3认同)
  • 它不是1比1的替换,因为async/await不会在单独的线程上执行操作.BackgroundWorker允许一个任务真正并行运行,其中async/await只是委托工作的顺序.现在,您是否真的需要一个单独的线程取决于您. (2认同)
  • @ xr280xr当您添加TPL的其余部分时,它*确实*实际上提供了所有这些功能.具体到你所指的是,`Task.Run`就是你需要创建一个`Task`来表示在另一个线程中完成的CPU绑定工作.我在问题中特别说过async/await*结合TPL*能够处理BGW的所有功能. (2认同)
  • 它旨在为那些使用 BackgroundWorker 作为使应用程序响应的解决方法的任务替换 BackgroundWorker。它不是真正的后台作品的替代品,那些需要在幕后运行的作品,可能会不时通知用户(自动保存、下载等)。 (2认同)
  • @ccalboni 这就是 `Progress` 类存在的目的。 (2认同)

Tom*_*myN 21

这是一个很好的介绍:http://msdn.microsoft.com/en-us/library/hh191443.aspx 线程部分正是您正在寻找的:

异步方法旨在实现非阻塞操作.异步方法中的await表达式在等待的任务运行时不会阻止当前线程.相反,表达式将方法的其余部分作为延续进行注册,并将控制权返回给异步方法的调用者.

async和await关键字不会导致创建其他线程.异步方法不需要多线程,因为异步方法不能在自己的线程上运行.该方法在当前同步上下文上运行,并仅在方法处于活动状态时在线程上使用时间.您可以使用Task.Run将CPU绑定的工作移动到后台线程,但后台线程无助于正在等待结果可用的进程.

在几乎所有情况下,基于异步的异步编程方法优于现有方法.特别是,对于IO绑定操作,此方法优于BackgroundWorker,因为代码更简单,您无需防范竞争条件.与Task.Run结合使用,异步编程优于BackgroundWorker用于CPU绑定操作,因为异步编程将运行代码的协调细节与Task.Run传输到线程池的工作分开.


Gen*_*нин 7

在.NET 4.5中,BackgroundWorker被明确标记为过时:

MSDN文章"使用异步和等待的异步编程(C#和Visual Basic)"告诉:

在几乎所有情况下,基于异步的异步编程方法优于现有方法.特别是,对于IO绑定操作,此方法优于BackgroundWorker , 因为代码更简单,您无需防范竞争条件.与Task.Run结合使用,异步编程优于BackgroundWorker 用于CPU绑定操作,因为异步编程将运行代码的协调细节与Task.Run传输到线程池的工作分开

UPDATE

  • 回应@ eran-otzap的评论:
    "对于IO绑定操作,因为代码更简单,你不必防范竞争条件"可以发生什么样的竞争条件,你能举个例子吗?"

这个问题应该作为一个单独的帖子.

维基百科对比赛条件有很好的解释.它的必要部分是多线程,并且来自同一篇MSDN文章异步编程与异步和等待(C#和Visual Basic):

异步方法旨在实现非阻塞操作.异步方法中的await表达式在等待的任务运行时不会阻止当前线程.相反,表达式将方法的其余部分作为延续进行注册,并将控制权返回给异步方法的调用者.

async和await关键字不会导致创建其他线程.异步方法不需要多线程,因为异步方法不能在自己的线程上运行.该方法在当前同步上下文上运行,并仅在方法处于活动状态时在线程上使用时间.您可以使用Task.Run将CPU绑定的工作移动到后台线程,但后台线程无助于正在等待结果可用的进程.

在几乎所有情况下,基于异步的异步编程方法优于现有方法.特别是,对于IO绑定操作,此方法优于BackgroundWorker,因为代码更简单,您无需防范竞争条件.与Task.Run结合使用,异步编程优于BackgroundWorker用于CPU绑定操作,因为异步编程将运行代码的协调细节与Task.Run传输到线程池的工作分开

也就是说,"async和await关键字不会导致创建其他线程".

据我一年前研究这篇文章时我记得自己的尝试,如果你运行并使用同一篇文章中的代码示例,你可能会遇到其非同步版本的情况(你可以尝试转换)它对你自己)无限制地阻止!

此外,对于具体示例,您可以搜索此站点.这是一些例子:

  • BackgrondWorker在.NET 4.5中没有明确标记为过时.MSDN文章仅表示使用异步方法可以更好地进行IO绑定操作 - 使用BackgroundWorker并不意味着您不能使用异步方法. (29认同)
  • 这个答案是对的.异步编程很容易在非平凡的程序中触发死锁.相比之下,BackgroundWorker简单而坚固. (9认同)
  • 我对该MSDN页面提出异议.首先,与BGW不再"协调",而不是使用任务.并且,是的BGW从未打算直接执行IO操作 - 总是*总是*比BGW更好的方式来做IO.另一个答案显示BGW使用起来并不比任务更复杂.如果您正确使用BGW,则没有竞争条件. (7认同)

The*_*ias 6

让我们之间的跟上时代的比较BackgroundWorkerTask.Run+ Progress<T>+异步/ AWAIT组合。我将使用这两种方法来实现模拟的 CPU 绑定操作,该操作必须卸载到后台线程,以保持 UI 响应。该操作的总持续时间为 5 秒,并且在操作期间ProgressBar必须每 500 毫秒更新一次。最后,计算结果必须显示在Label. 先说BackgroundWorker实现:

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();
}
Run Code Online (Sandbox Code Playgroud)

事件处理程序中的 24 行代码。现在让我们用现代方法做同样的事情:

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}";
}
Run Code Online (Sandbox Code Playgroud)

事件处理程序中的 17 行代码。整体代码少得多。

在这两种情况下,工作都是在ThreadPool线程上执行的。

BackgroundWorker方法的优点:

  1. 可用于面向.NET Framework 4.0及更早版本的项目。

在优势Task.Run+ Progress<T>+ async/await方法:

  1. 结果是强类型的。无需从object. 没有运行时的风险InvalidCastException
  2. 工作完成后的continuation 是在原来的范围内运行,而不是在一个lamda 里面。
  3. 允许通过Progress. 相反, aBackgroundWorker强制您将任何额外信息作为 传递object,然后从object ProgressChangedEventArgs.UserState属性中返回。
  4. 允许使用多个Progress对象,轻松报告不同频率的不同进度数据。这对于BackgroundWorker.
  5. 取消操作遵循协作取消标准 .NET 模式CancellationTokenSource+CancellationToken组合。目前有数以千计的 .NET API 使用CancellationToken. 相反,BackgroundWorkers 取消机制不能被消耗,因为它不生成通知。
  6. 最后Task.Run,它同样轻松地支持同步和异步工作负载。在BackgroundWorker只能通过阻断工作线程异步消耗的API。