首页 文章

Async / await vs BackgroundWorker

提问于
浏览
139

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

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

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

4 回答

  • 5

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

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

  • 66

    对于很多人来说,这很可能是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);
    

    注意两种语法之间比较处理的不相交性以及如何在没有 async / await 的情况下不能使用 using .

    但是,你不会用 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();
    

    你真的没有什么可以使用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);
    

    在这种情况下 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 ...即你赢了't manually create the object, set the properties and set the event handlers. you'只能填充正文的 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表面的能力,那么由读者决定哪个是"better" . 但是,对我来说,这是 awaitBackgroundWorker 之间的比较,而不是你是否可以等待像 Stream.ReadAsync 这样的内置方法 . 例如如果您按预期使用 BackgroundWorker ,则可能很难转换为使用 await .

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

  • 177

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

    异步方法旨在实现非阻塞操作 . 异步方法中的await表达式在等待的任务运行时不会阻止当前线程 . 相反,表达式将方法的其余部分作为延续进行注册,并将控制权返回给异步方法的调用者 . async和await关键字不会导致创建其他线程 . 异步方法不需要多线程,因为异步方法不能在自己的线程上运行 . 该方法在当前同步上下文上运行,并仅在方法处于活动状态时在线程上使用时间 . 您可以使用Task.Run将CPU绑定的工作移动到后台线程,但后台线程无助于正在等待结果可用的进程 . 在几乎所有情况下,基于异步的异步编程方法优于现有方法 . 特别是,对于IO绑定操作,此方法优于BackgroundWorker,因为代码更简单,您无需防范竞争条件 . 与Task.Run结合使用,异步编程优于BackgroundWorker用于CPU绑定操作,因为异步编程将运行代码的协调细节与Task.Run传输到线程池的工作分开 .

  • 19

    BackgroundWorker在.NET 4.5中明确标记为已废弃:

    MSDN文章"Asynchronous Programming with Async and Await (C# and Visual Basic)"告诉:

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

    UPDATE

    • 回应@eran-otzap的评论:
      "for IO-bound operations because the code is simpler and you don't have to guard against race conditions"可能会出现什么样的竞争条件,你举个例子吗? “

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

    维基百科对racing条件有很好的解释 . 它的必要部分是多线程,并且来自同一篇MSDN文章Asynchronous Programming with Async and Await (C# and Visual Basic)

    异步方法旨在实现非阻塞操作 . 异步方法中的await表达式在等待的任务运行时不会阻止当前线程 . 相反,表达式将方法的其余部分作为延续进行注册,并将控制权返回给异步方法的调用者 . async和await关键字不会导致创建其他线程 . 异步方法不需要多线程,因为异步方法不能在自己的线程上运行 . 该方法在当前同步上下文上运行,并仅在方法处于活动状态时在线程上使用时间 . 您可以使用Task.Run将CPU绑定的工作移动到后台线程,但后台线程无助于正在等待结果可用的进程 . 在几乎所有情况下,基于异步的异步编程方法优于现有方法 . 特别是,对于IO绑定操作,此方法优于BackgroundWorker,因为代码更简单,您无需防范竞争条件 . 与Task.Run结合使用,异步编程优于BackgroundWorker用于CPU绑定操作,因为异步编程将运行代码的协调细节与Task.Run传输到线程池的工作分开

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

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

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

相关问题