asynchronous/等待与BackgroundWorker

在过去的几天里,我testing了.net 4.5和c#5的新特性。

我喜欢它的新asynchronous/等待function。 早些时候,我曾经使用BackgroundWorker在响应式UI的背景下处理更长的stream程。

我的问题是:有了这些很好的新function,什么时候应该使用async / await和BackgroundWorker ? 哪两个都是常见的情况?

asynchronous/等待被devise来replace诸如BackgroundWorker 。 虽然你当然可以使用它,如果你想,你应该能够使用async / await和其他一些TPL工具来处理所有的东西。

既然这两者都起作用,那就归结为你个人使用什么时候的个人偏好。 什么更快? 你更容易理解什么?

这很可能是TL; DR,对于很多人来说,但我认为比较awaitBackgroundWorker就像比较苹果和橙子,我的想法如下:

BackgroundWorker是为了模拟一个你想在后台执行的任务,在线程池线程上。 async / await是asynchronous操作asynchronous等待的语法。 这些操作可能会或可能不会使用线程池线程,甚至不使用任何其他线程 。 所以,他们是苹果和橘子。

例如,你可以像await一样做下面的事情:

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

但是,你可能永远不会在后台工作人员中build模,你可能会在.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情况下使用。

但是,你不会用BackgroundWorker做这样的事情。 BackgroundWorker通常用于build模您不想影响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(); 

没有什么可以使用asynchronous/等待, BackgroundWorker正在为您创build线程。

现在,你可以使用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正在为您创build线程(假定默认的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 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(); 

但是,你不会处理这个问题,因为你需要将后台工作者组件拖放到表单的devise表面上,这是你不能用async / awaitTask …做的事情。您将不会手动创build对象,设置属性并设置事件处理程序。 您只需填写DoWorkRunWorkerCompletedProgressChanged事件处理程序的主体。

如果你“转换”为asynchronous/等待,你会做这样的事情:

  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" 

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

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

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

asynchronous方法旨在成为非阻塞操作。 asynchronous方法中的awaitexpression式在等待执行的任务正在运行时不会阻塞当前线程。 expression式将该方法的其余部分注册为continuation,并将控制权返回给asynchronous方法的调用者。

asynchronous和等待关键字不会导致创build额外的线程。 asynchronous方法不需要multithreading,因为asynchronous方法不能在自己的线程上运行。 该方法在当前同步上下文上运行,并仅在该方法处于活动状态时才在线程上使用时间。 您可以使用Task.Run将CPU绑定的工作移至后台线程,但后台线程无助于只等待结果可用的进程。

asynchronous编程的asynchronous方法比几乎所有情况下的现有方法更可取。 特别是,这种方法比BackgroundWorker对于IO绑定操作更好,因为代码更简单,您不必防范竞争条件。 与Task.Run相结合,asynchronous编程优于BackgroundWorker的CPU绑定操作,因为asynchronous编程将从Task.Run传送到线程池的工作中分离运行代码的协调细节。

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

  • 在书中By Joseph Albahari,Ben Albahari“C#5.0 in a Nutshell:The Definitive Reference”
  • Stephen Cleary对我的问题的回答是不是.NET 4.0 TPL使APM,EAP和BackgroundWorkerasynchronous模式过时了?

MSDN文章“asynchronous编程与asynchronous和等待(C#和Visual Basic)”告诉:

asynchronous编程的asynchronous方法比几乎所有情况下的现有方法更可取 。 特别是,这种方法比BackgroundWorker 对于IO绑定操作更好因为代码更简单,您不必防范竞争条件。 与Task.Run相结合,asynchronous编程优于BackgroundWorker 的CPU绑定操作,因为asynchronous编程将运行代码的协调细节从Task.Run传输到线程池的工作中分离出来

UPDATE

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

这个问题应该作为一个单独的职位。

维基百科有一个很好的赛车条件的解释。 它的必要部分是multithreading,并从相同的MSDN文章Async和Awaitasynchronous编程(C#和Visual Basic) :

asynchronous方法旨在成为非阻塞操作。 asynchronous方法中的awaitexpression式在等待执行的任务正在运行时不会阻塞当前线程。 expression式将该方法的其余部分注册为continuation,并将控制权返回给asynchronous方法的调用者。

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

asynchronous编程的asynchronous方法比几乎所有情况下的现有方法更可取。 特别是,这种方法比BackgroundWorker对于IO绑定操作更好,因为代码更简单,您不必防范竞争条件。 与Task.Run相结合,asynchronous编程优于BackgroundWorker的CPU绑定操作,因为asynchronous编程将运行代码的协调细节从Task.Run传输到线程池的工作中分离出来

也就是说,“asynchronous和等待关键字不会导致创build额外的线程”。

就我一年前学习这篇文章而言,我可以回想起自己的尝试,如果你已经运行并使用同一篇文章中的代码示例,那么可能会碰到它的非asynchronous版本(你可以尝试转换它给你自己)无限期阻止!

此外,具体的例子,你可以search这个网站。 这里有一些例子:

  • 使用c#5.0调用asynchronous方法
  • 为什么这个代码在通过TPL / Tasks执行时失败?
  • 等待vs Task.Wait – 死锁?