异步/等待与BackgroundWorker


162

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

我喜欢它的新异步/等待功能。之前,我曾使用BackgroundWorker通过响应UI在后台处理更长的进程。

我的问题是:有了这些不错的新功能之后,什么时候应该使用async / await以及什么时候使用BackgroundWorker?两者都有哪些常见方案?



两者都很好,但是如果您使用的旧版本代码尚未迁移到更高版本的.net,则应使用该代码。BackgroundWorker在这两者上均可工作。
dcarl661 '18

Answers:


74

async / await旨在替代诸如的构造BackgroundWorker。虽然当然可以使用它,但应该可以使用async / await以及其他一些TPL工具来处理其中的所有内容。

由于两者都可以工作,因此可以根据个人喜好决定何时使用。什么是更快的选择?什么让更容易理解?


17
谢谢。对我而言,异步/等待似乎更加清晰和“自然”。我认为BakcgoundWorker使代码“嘈杂”。
汤姆(Tom)

12
@Tom好吧,这就是Microsoft花费大量时间和精力来实施它的原因。如果没有更好的话,他们也不会打扰
Servy 2012年

5
是。新的等待内容使旧的BackgroundWorker显得完全落后和过时。区别在于戏剧性。
usr

16
比较后台任务的不同方法时,我的博客上有一个不错的摘要。注意async/ await还允许在没有线程池线程的情况下进行异步编程。
Stephen Cleary 2012年

8
否决了这个答案,这是一种误导。Async / await并非旨在替代后台工作程序。
Quango 2014年

206

这可能是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);
    }
}

但是,您可能永远不会在后台工作器中对它进行建模,而可能在.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);

请注意,比较这两种语法之间的不相交关系,以及using没有async/时如何使用await

但是,您不会使用来做类似的事情BackgroundWorkerBackgroundWorker通常用于为您不想影响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();

确实没有什么可以与异步/等待一起使用的,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);

在这种情况下,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"

我认为,主要的比较是您是否报告进度。例如,您可能有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();

但是,您将无法处理其中的某些问题,因为您将背景工作程序组件拖放到了表单的设计图面上–使用async/ awaitTask... 不能执行的操作,也就是说,您不会手动创建对象,设置属性和设置事件处理程序。你只填写的身体DoWorkRunWorkerCompletedProgressChanged事件处理程序。

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

     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"

没有将组件拖到Designer曲面上的能力,实际上取决于读者来决定哪个“更好”。但是,对我而言,这是和之间的比较,awaitBackgroundWorker不是您是否可以等待诸如的内置方法Stream.ReadAsync。例如,如果您BackgroundWorker按预期使用,可能很难转换为use await

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


2
我认为async / await存在一个缺陷是您可能想一次启动多个异步任务。等待是指在开始下一个任务之前等待每个任务完成。如果省略await关键字,则该方法将同步运行,这不是您想要的。我认为异步/等待不能解决诸如“启动这5个任务并在每个任务没有特定顺序完成时给我回电”之类的问题。
Trevor Elliott

4
@Moozhe。不正确,您可以做var t1 = webReq.GetResponseAsync(); var t2 = webReq2.GetResponseAsync(); await t1; await t2;。等待两个并行操作。IMO对于异步但顺序任务来说,等待要好得多
彼得·里奇

2
@Moozhe是的,这样做可以保持一定的顺序-正如我所提到的。这是等待的主要目的是在顺序代码中获得异步性。当然,您可以await Task.WhenAny(t1, t2)先执行任一个任务,然后再执行某项操作。您可能希望循环以确保其他任务也完成。通常,您想知道特定任务何时完成,这导致您编写顺序awaits。
彼得·里奇


5
诚实,BackgroundWorker 从来都不适合进行IO绑定操作。
彼得·里奇

21

这是一个很好的介绍:http : //msdn.microsoft.com/zh-cn/library/hh191443.aspx “线程”部分正是您想要的:

异步方法旨在作为非阻塞操作。在等待任务运行时,异步方法中的等待表达式不会阻塞当前线程。取而代之的是,表达式将方法的其余部分作为继续进行签名,并将控制权返回给异步方法的调用者。

async和await关键字不会导致创建其他线程。异步方法不需要多线程,因为异步方法不会在自己的线程上运行。该方法在当前同步上下文上运行,并且仅在该方法处于活动状态时才在线程上使用时间。您可以使用Task.Run将受CPU约束的工作移至后台线程,但是后台线程对仅等待结果可用的进程没有帮助。

在几乎每种情况下,基于异步的异步编程方法都优于现有方法。尤其是,此方法比IO绑定操作的BackgroundWorker更好,因为代码更简单,而且您不必防范竞争条件。与Task.Run结合使用时,异步编程比BackgroundWorker在CPU绑定操作方面更好,因为异步编程将运行代码的协调细节与Task.Run转移到线程池的工作分开。


“用于IO绑定操作,因为代码更简单,您不必防范竞争条件。”会发生什么竞争条件,您能举个例子吗?
eran otzap

8

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

MSDN文章“使用Async和Await进行异步编程(C#和Visual Basic)”告诉:

在几乎每种情况下,基于异步的异步编程方法都优于现有方法。尤其是,此方法比IO绑定操作的BackgroundWorker 更好 因为代码更简单,而且您不必防范竞争条件。与Task.Run结合使用时,异步编程比BackgroundWorker 在CPU绑定操作方面更好因为异步编程将运行代码的协调细节与Task.Run转移到线程池的工作分开

更新

  • 回应@ eran-otzap的评论:
    “用于IO绑定操作,因为代码更简单,您不必防范竞争条件。”会发生什么竞争条件,您能举个例子吗?”

这个问题应该放在单独的位置上。

维基百科对比赛条件有很好的解释。它的必要部分是多线程,并且来自同一MSDN文章《使用Async和Await进行异步编程》(C#和Visual Basic)

异步方法旨在作为非阻塞操作。在等待任务运行时,异步方法中的等待表达式不会阻塞当前线程。取而代之的是,表达式将方法的其余部分作为继续进行签名,并将控制权返回给异步方法的调用者。

async和await关键字不会导致创建其他线程。异步方法不需要多线程,因为异步方法不会在自己的线程上运行。该方法在当前同步上下文上运行,并且仅在该方法处于活动状态时才在线程上使用时间。您可以使用Task.Run将受CPU约束的工作移至后台线程,但是后台线程对仅等待结果可用的进程没有帮助。

在几乎每种情况下,基于异步的异步编程方法都优于现有方法。尤其是,此方法比IO绑定操作的BackgroundWorker更好,因为代码更简单,而且您不必防范竞争条件。与Task.Run结合使用时,异步编程比BackgroundWorker在CPU绑定操作方面更好,因为异步编程将运行代码的协调细节与Task.Run转移到线程池的工作分开

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

据我回想起一年前研究本文时的尝试,如果您已经运行并玩过同一篇文章的代码示例,则可能会遇到其非异步版本的情况(可以尝试转换自己屏蔽)无限期屏蔽!

另外,有关具体示例,您可以搜索此站点。这是一些例子:


30
在.NET 4.5中,BackgrondWorker 明确标记为过时。MSDN文章只说使用异步方法更好地进行IO绑定操作-使用BackgroundWorker并不意味着您不能使用异步方法。
彼得·里奇

@PeterRitchie,我纠正了我的回答。对我来说,“现有方法已过时”是“几乎在每种情况下基于异步方法的异步编程都比现有方法更可取”的代名词
Gennady VaninГеннадийВанин

7
我对该MSDN页面不满意。一方面,您与BGW进行的“协调”不比对Task进行的更多。而且,是的,BGW从未打算直接执行IO操作- 始终比BGW更好的做IO的方法。另一个答案表明BGW的使用并不比Task更复杂。如果正确使用BGW,则没有竞争条件。
彼得·里奇

“用于IO绑定操作,因为代码更简单,您不必防范竞争条件。”会发生什么竞争条件,您能举个例子吗?
eran otzap

11
这个答案是错误的。异步编程很容易在非平凡程序中触发死锁。相比之下,BackgroundWorker简单且坚如磐石。
ZunTzu
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.