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为多,但是,我认为比较await有BackgroundWorker就是喜欢上了这后续比较苹果和桔子和我的想法:
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)
请注意比较了两种语法,以及如何你不能使用的处置不相交using无async/ 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/ await和Task...即你赢了" t手动创建对象,设置属性并设置事件处理程序.你只填写的身体DoWork,RunWorkerCompleted和ProgressChanged事件处理程序.
如果您将其"转换"为异步/等待,您可以执行以下操作:
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表面的能力,那么由读者来决定哪个"更好".但是,对我来说,这是和之间的比较,await而BackgroundWorker不是你是否可以等待内置方法Stream.ReadAsync.例如,如果您BackgroundWorker按预期使用,可能很难转换为使用await.
其他想法:http://jeremybytes.blogspot.ca/2012/05/backgroundworker-component-im-not-dead.html
Ser*_*rvy 73
async/await旨在替换诸如BackgroundWorker.之类的结构.如果你愿意,你当然可以使用它,你应该能够使用async/await和其他一些TPL工具来处理那里的所有内容.
由于两者都起作用,因此归结为个人偏好,以及您何时使用.什么对你更快?什么是更容易为你理解吗?
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传输到线程池的工作分开.
在.NET 4.5中,BackgroundWorker被明确标记为过时:
MSDN文章"使用异步和等待的异步编程(C#和Visual Basic)"告诉:
在几乎所有情况下,基于异步的异步编程方法优于现有方法.特别是,对于IO绑定操作,此方法优于BackgroundWorker , 因为代码更简单,您无需防范竞争条件.与Task.Run结合使用,异步编程优于BackgroundWorker 用于CPU绑定操作,因为异步编程将运行代码的协调细节与Task.Run传输到线程池的工作分开
UPDATE
这个问题应该作为一个单独的帖子.
维基百科对比赛条件有很好的解释.它的必要部分是多线程,并且来自同一篇MSDN文章异步编程与异步和等待(C#和Visual Basic):
异步方法旨在实现非阻塞操作.异步方法中的await表达式在等待的任务运行时不会阻止当前线程.相反,表达式将方法的其余部分作为延续进行注册,并将控制权返回给异步方法的调用者.
async和await关键字不会导致创建其他线程.异步方法不需要多线程,因为异步方法不能在自己的线程上运行.该方法在当前同步上下文上运行,并仅在方法处于活动状态时在线程上使用时间.您可以使用Task.Run将CPU绑定的工作移动到后台线程,但后台线程无助于正在等待结果可用的进程.
在几乎所有情况下,基于异步的异步编程方法优于现有方法.特别是,对于IO绑定操作,此方法优于BackgroundWorker,因为代码更简单,您无需防范竞争条件.与Task.Run结合使用,异步编程优于BackgroundWorker用于CPU绑定操作,因为异步编程将运行代码的协调细节与Task.Run传输到线程池的工作分开
也就是说,"async和await关键字不会导致创建其他线程".
据我一年前研究这篇文章时我记得自己的尝试,如果你运行并使用同一篇文章中的代码示例,你可能会遇到其非同步版本的情况(你可以尝试转换)它对你自己)无限制地阻止!
此外,对于具体示例,您可以搜索此站点.这是一些例子:
让我们之间的跟上时代的比较BackgroundWorker和Task.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方法的优点:
在优势Task.Run+ Progress<T>+ async/await方法:
object. 没有运行时的风险InvalidCastException。Progress. 相反, aBackgroundWorker强制您将任何额外信息作为 传递object,然后从object ProgressChangedEventArgs.UserState属性中返回。Progress对象,轻松报告不同频率的不同进度数据。这对于BackgroundWorker.CancellationTokenSource+CancellationToken组合。目前有数以千计的 .NET API 使用CancellationToken. 相反,BackgroundWorkers 取消机制不能被消耗,因为它不生成通知。Task.Run,它同样轻松地支持同步和异步工作负载。在BackgroundWorker只能通过阻断工作线程异步消耗的API。| 归档时间: |
|
| 查看次数: |
75587 次 |
| 最近记录: |