在过去的几天里,我测试了 .net 4.5 和 c# 5 的新特性。
我喜欢它的新异步/等待功能。早些时候,我使用BackgroundWorker来处理具有响应式 UI 的后台较长进程。
我的问题是:在有了这些不错的新功能之后,我应该什么时候使用 async/await 以及什么时候使用BackgroundWorker?两者的常见情况是什么?
在过去的几天里,我测试了 .net 4.5 和 c# 5 的新特性。
我喜欢它的新异步/等待功能。早些时候,我使用BackgroundWorker来处理具有响应式 UI 的后台较长进程。
我的问题是:在有了这些不错的新功能之后,我应该什么时候使用 async/await 以及什么时候使用BackgroundWorker?两者的常见情况是什么?
对于许多人来说,这可能是 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
将为您创建线程(假设为 default 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
... 做的事情,即您不会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"
如果无法将组件拖到 Designer 表面,则真正由读者决定哪个“更好”。但是,对我来说,这是和之间的比较,await
而BackgroundWorker
不是你是否可以等待内置方法,如Stream.ReadAsync
. 例如,如果您BackgroundWorker
按预期使用,则可能很难转换为使用await
.
其他想法:http: //jeremybytes.blogspot.ca/2012/05/backgroundworker-component-im-not-dead.html
async/await 旨在替换诸如BackgroundWorker
. 虽然您当然可以根据需要使用它,但您应该能够使用 async/await 以及其他一些 TPL 工具来处理所有可用的东西。
由于两者都有效,因此取决于您何时使用的个人偏好。什么对你来说更快?你更容易理解什么?
这是一个很好的介绍: http: //msdn.microsoft.com/en-us/library/hh191443.aspx 线程部分正是您正在寻找的:
异步方法旨在成为非阻塞操作。当等待的任务正在运行时,异步方法中的等待表达式不会阻塞当前线程。相反,表达式将方法的其余部分注册为延续,并将控制权返回给异步方法的调用者。
async 和 await 关键字不会导致创建额外的线程。异步方法不需要多线程,因为异步方法不在其自己的线程上运行。该方法在当前同步上下文上运行,并且仅在该方法处于活动状态时才使用线程上的时间。您可以使用 Task.Run 将受 CPU 限制的工作转移到后台线程,但后台线程对等待结果可用的进程没有帮助。
在几乎所有情况下,基于异步的异步编程方法都优于现有方法。特别是,对于 IO 绑定操作,这种方法比 BackgroundWorker 更好,因为代码更简单,并且您不必防范竞争条件。与 Task.Run 结合使用时,异步编程比 BackgroundWorker 更适合 CPU 密集型操作,因为异步编程将运行代码的协调细节与 Task.Run 传输到线程池的工作分开。
BackgroundWorker在 .NET 4.5 中被明确标记为已过时:
MSDN 文章“使用 Async 和 Await 进行异步编程(C# 和 Visual Basic)”讲述:
在几乎所有情况下,基于异步的异步编程方法都优于现有方法。特别是,对于 IO 绑定操作,这种方法比BackgroundWorker 更好, 因为代码更简单,并且您不必防范竞争条件。与 Task.Run 结合使用时,异步编程在 CPU 密集型操作方面优于BackgroundWorker ,因为异步编程将运行代码的协调细节与Task.Run转移到线程池的工作分开
更新
这个问题应该作为一个单独的帖子。
维基百科对比赛条件有很好的解释。它的必要部分是多线程,来自同一篇 MSDN 文章Asynchronous Programming with Async and Await (C# and Visual Basic):
异步方法旨在成为非阻塞操作。当等待的任务正在运行时,异步方法中的等待表达式不会阻塞当前线程。相反,表达式将方法的其余部分注册为延续,并将控制权返回给异步方法的调用者。
async 和 await 关键字不会导致创建额外的线程。异步方法不需要多线程,因为异步方法不在其自己的线程上运行。该方法在当前同步上下文上运行,并且仅在该方法处于活动状态时才使用线程上的时间。您可以使用 Task.Run 将受 CPU 限制的工作转移到后台线程,但后台线程对等待结果可用的进程没有帮助。
在几乎所有情况下,基于异步的异步编程方法都优于现有方法。特别是,对于 IO 绑定操作,这种方法比 BackgroundWorker 更好,因为代码更简单,并且您不必防范竞争条件。与 Task.Run 结合使用时,异步编程在 CPU 密集型操作方面优于 BackgroundWorker,因为异步编程将运行代码的协调细节与 Task.Run 转移到线程池的工作分开
也就是说,“async 和 await 关键字不会导致创建额外的线程”。
据我记得一年前我在学习这篇文章时自己的尝试,如果你已经运行并玩过同一篇文章中的代码示例,你可能会遇到它的非异步版本(你可以尝试转换它给自己)无限期地阻止!
此外,对于具体示例,您可以搜索此站点。下面是一些例子:
让我们对aBackgroundWorker
和Task.Run
++ async/await组合进行最新比较。我将使用这两种方法来实现必须卸载到后台线程的模拟 CPU 绑定操作,以保持 UI 响应。该操作的总持续时间为 5 秒,并且在操作期间必须每 500 毫秒更新一次。最后,计算结果必须显示在. 首先是实现:Progress<T>
ProgressBar
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
. 相反, aBackgroundWorker
迫使您将任何额外信息作为 传递object
,然后从object
ProgressChangedEventArgs.UserState
属性中回退。Progress
对象,以不同的频率报告不同的进度数据,轻松。这是非常乏味且容易出错的BackgroundWorker
.CancellationTokenSource
+CancellationToken
组合。目前有数千个 .NET API 使用CancellationToken
. 相反,BackgroundWorker
s 取消机制不能被使用,因为它不生成通知。Task.Run
,它同样轻松地支持同步和异步工作负载。BackgroundWorker
只能通过阻塞工作线程来使用异步 API 。