在过去的几天里,我测试了.net 4.5和c#5的新功能。
我喜欢它的新async / await功能。之前我曾使用BackgroundWorker在后台处理响应式UI的更长进程。
我的问题是:在拥有这些不错的新功能之后,我何时应该使用async / await和BackgroundWorker?这两种情况的常见情况是什么?
答案 0 :(得分:190)
这可能是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);
}
}
但是,您可能永远不会在后台工作程序中对此进行建模,您可能会在.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
。
但是,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
/无法做到的事情。 await
和Task
...即您不会手动创建对象,设置属性并设置事件处理程序。您只需填写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"
如果没有将组件拖到Designer表面的能力,那么由读者决定哪个“更好”。但是,对我来说,这是await
和BackgroundWorker
之间的比较,而不是您是否可以等待Stream.ReadAsync
等内置方法。例如如果您按预期使用BackgroundWorker
,则可能很难转换为使用await
。
其他想法:http://jeremybytes.blogspot.ca/2012/05/backgroundworker-component-im-not-dead.html
答案 1 :(得分:69)
async / await旨在替换BackgroundWorker
等构造。虽然你肯定可以使用它,但你应该能够使用async / await和其他一些TPL工具来处理那里的一切。
由于两者都起作用,因此归结为个人偏好,以及您何时使用。 你的速度更快? 你更容易理解?
答案 2 :(得分:20)
这是一个很好的介绍:http://msdn.microsoft.com/en-us/library/hh191443.aspx 线程部分正是您所寻找的:
异步方法旨在实现非阻塞操作。在等待任务运行时,异步方法中的await表达式不会阻止当前线程。相反,表达式将方法的其余部分作为延续进行注册,并将控制权返回给异步方法的调用者。
async和await关键字不会导致创建其他线程。异步方法不需要多线程,因为异步方法不能在自己的线程上运行。该方法在当前同步上下文上运行,并仅在方法处于活动状态时在线程上使用时间。您可以使用Task.Run将受CPU限制的工作移动到后台线程,但后台线程对于只等待结果可用的进程没有帮助。
基于异步的异步编程方法几乎在所有情况下都优于现有方法。特别是,对于IO绑定操作,此方法优于BackgroundWorker,因为代码更简单,您无需防范竞争条件。与Task.Run结合使用,异步编程优于BackgroundWorker用于CPU绑定操作,因为异步编程将运行代码的协调细节与Task.Run传输到线程池的工作分开。
答案 3 :(得分:5)
BackgroundWorker在.NET 4.5中明确标记为过时:
MSDN文章"Asynchronous Programming with Async and Await (C# and Visual Basic)"告诉我们:
异步编程的基于异步的方法更可取 几乎在所有情况下都采用现有方法。特别是这个 对于IO绑定操作,方法优于BackgroundWorker 因为代码更简单,你不必防范种族 条件。结合Task.Run,异步编程更好 比BackgroundWorker 用于CPU绑定操作,因为异步 编程分离运行代码的协调细节 从 Task.Run 转移到线程池
的工作
<强>更新强>
这个问题应该作为一个单独的帖子。
维基百科对racing条件有很好的解释。 它的必要部分是多线程,并且来自同一篇MSDN文章Asynchronous Programming with Async and Await (C# and Visual Basic):
异步方法旨在实现非阻塞操作。等待 异步方法中的表达式不会阻塞当前线程 等待的任务正在运行。相反,表达式表示其余部分 该方法作为一个延续并将控制权返回给调用者 异步方法。
async和await关键字不会导致其他线程 创建。异步方法不需要多线程,因为异步 方法不会在自己的线程上运行。该方法在当前运行 同步上下文并仅在线程上使用时间 方法是有效的。您可以使用Task.Run将CPU绑定的工作移动到 后台线程,但后台线程对进程没有帮助 那只是等待结果可用。
基于异步的异步编程方法比较好 几乎在每种情况下现有的方法。特别是这种方法 对于IO绑定操作,它比BackgroundWorker更好,因为 代码更简单,您不必防范竞争条件。 与Task.Run结合使用,异步编程优于 BackgroundWorker用于CPU绑定操作,因为异步编程 将运行代码的协调细节与工作分开 Task.Run转移到线程池
即,“async和await关键字不会导致创建其他线程”。
据我一年前研究这篇文章时我记得自己的尝试,如果你运行并使用同一篇文章中的代码示例,你可能会遇到其非同步版本的情况(你可以尝试将其转换为自己)无限期阻止!
此外,对于具体示例,您可以搜索此站点。以下是一些例子:
答案 4 :(得分:1)
让我们在BackgroundWorker
与Task.Run
+ Progress<T>
+ async/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();
}
事件处理程序中的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}";
}
事件处理程序中的17行代码。总体来说,代码要少得多。
在两种情况下,工作都是在ThreadPool
线程上执行的。
BackgroundWorker
方法的优点:
Task.Run
+ Progress<T>
+ async
/ await
方法的优点:
object
进行投射。没有运行时InvalidCastException
的风险。Progress
报告任意强类型信息。相反,BackgroundWorker
会强制您以object
的形式传递任何额外的信息,然后从object
ProgressChangedEventArgs.UserState
属性进行回退。Progress
对象,轻松地报告具有不同频率的不同进度数据。 BackgroundWorker
非常繁琐且容易出错。CancellationTokenSource
+ CancellationToken
组合键。
当前,有成千上万个消耗CancellationToken
的.NET API。相反,BackgroundWorker
的取消机制无法使用,因为它不会生成通知。Task.Run
同样轻松地支持同步和异步工作负载。 BackgroundWorker
只能通过阻止工作线程来使用异步API。