在过去的几天中,我测试了.net 4.5和c#5的新功能。
我喜欢它的新异步/等待功能。之前,我曾使用BackgroundWorker通过响应UI在后台处理更长的进程。
我的问题是:有了这些不错的新功能之后,什么时候应该使用async / await以及什么时候使用BackgroundWorker?两者都有哪些常见方案?
在过去的几天中,我测试了.net 4.5和c#5的新功能。
我喜欢它的新异步/等待功能。之前,我曾使用BackgroundWorker通过响应UI在后台处理更长的进程。
我的问题是:有了这些不错的新功能之后,什么时候应该使用async / await以及什么时候使用BackgroundWorker?两者都有哪些常见方案?
Answers:
async / await旨在替代诸如的构造BackgroundWorker
。虽然当然可以使用它,但应该可以使用async / await以及其他一些TPL工具来处理其中的所有内容。
由于两者都可以工作,因此可以根据个人喜好决定何时使用。什么是更快的选择?什么让您更容易理解?
这可能是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();
确实没有什么可以与异步/等待一起使用的,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
按预期使用,可能很难转换为use await
。
其他想法:http : //jeremybytes.blogspot.ca/2012/05/backgroundworker-component-im-not-dead.html
var t1 = webReq.GetResponseAsync(); var t2 = webReq2.GetResponseAsync(); await t1; await t2;
。等待两个并行操作。IMO对于异步但顺序任务来说,等待要好得多
await Task.WhenAny(t1, t2)
先执行任一个任务,然后再执行某项操作。您可能希望循环以确保其他任务也完成。通常,您想知道特定任务何时完成,这导致您编写顺序await
s。
这是一个很好的介绍:http : //msdn.microsoft.com/zh-cn/library/hh191443.aspx “线程”部分正是您想要的:
异步方法旨在作为非阻塞操作。在等待任务运行时,异步方法中的等待表达式不会阻塞当前线程。取而代之的是,表达式将方法的其余部分作为继续进行签名,并将控制权返回给异步方法的调用者。
async和await关键字不会导致创建其他线程。异步方法不需要多线程,因为异步方法不会在自己的线程上运行。该方法在当前同步上下文上运行,并且仅在该方法处于活动状态时才在线程上使用时间。您可以使用Task.Run将受CPU约束的工作移至后台线程,但是后台线程对仅等待结果可用的进程没有帮助。
在几乎每种情况下,基于异步的异步编程方法都优于现有方法。尤其是,此方法比IO绑定操作的BackgroundWorker更好,因为代码更简单,而且您不必防范竞争条件。与Task.Run结合使用时,异步编程比BackgroundWorker在CPU绑定操作方面更好,因为异步编程将运行代码的协调细节与Task.Run转移到线程池的工作分开。
在.NET 4.5中,BackgroundWorker被明确标记为过时:
MSDN文章“使用Async和Await进行异步编程(C#和Visual Basic)”告诉:
在几乎每种情况下,基于异步的异步编程方法都优于现有方法。尤其是,此方法比IO绑定操作的BackgroundWorker 更好, 因为代码更简单,而且您不必防范竞争条件。与Task.Run结合使用时,异步编程比BackgroundWorker 在CPU绑定操作方面更好,因为异步编程将运行代码的协调细节与Task.Run转移到线程池的工作分开
更新
这个问题应该放在单独的位置上。
维基百科对比赛条件有很好的解释。它的必要部分是多线程,并且来自同一MSDN文章《使用Async和Await进行异步编程》(C#和Visual Basic):
异步方法旨在作为非阻塞操作。在等待任务运行时,异步方法中的等待表达式不会阻塞当前线程。取而代之的是,表达式将方法的其余部分作为继续进行签名,并将控制权返回给异步方法的调用者。
async和await关键字不会导致创建其他线程。异步方法不需要多线程,因为异步方法不会在自己的线程上运行。该方法在当前同步上下文上运行,并且仅在该方法处于活动状态时才在线程上使用时间。您可以使用Task.Run将受CPU约束的工作移至后台线程,但是后台线程对仅等待结果可用的进程没有帮助。
在几乎每种情况下,基于异步的异步编程方法都优于现有方法。尤其是,此方法比IO绑定操作的BackgroundWorker更好,因为代码更简单,而且您不必防范竞争条件。与Task.Run结合使用时,异步编程比BackgroundWorker在CPU绑定操作方面更好,因为异步编程将运行代码的协调细节与Task.Run转移到线程池的工作分开
也就是说,“ async和await关键字不会导致创建其他线程”。
据我回想起一年前研究本文时的尝试,如果您已经运行并玩过同一篇文章的代码示例,则可能会遇到其非异步版本的情况(可以尝试转换自己屏蔽)无限期屏蔽!
另外,有关具体示例,您可以搜索此站点。这是一些例子: