我一直在考虑C#5中新的异步内容,并提出了一个特别的问题。
我理解await
关键字是一个简洁的编译器技巧/语法糖来实现continuation passing,其中方法的其余部分被分解为Task
个对象并排队到按顺序运行,但控制权返回给调用方法。
我的问题是我听说目前这一切都在一个线程上。这是否意味着这种异步内容实际上只是将延续代码转换为Task
个对象,然后在每个任务完成之后调用Application.DoEvents()
然后启动下一个任务?
或者我错过了什么? (问题的这一部分是修辞性的 - 我完全清楚我错过了某种东西 :))
答案 0 :(得分:51)
它是并发,在某种意义上,许多未完成的异步操作可能随时都在进行中。它可能是也可能不是多线程。
默认情况下,await
会将延续计划回“当前执行上下文”。 “当前执行上下文”如果不是SynchronizationContext.Current
则定义为null
,如果没有TaskScheduler.Current
则定义为SynchronizationContext
。
您可以通过调用ConfigureAwait
并为false
参数传递continueOnCapturedContext
来覆盖此默认行为。在这种情况下,继续将不会被安排回该执行上下文。这通常意味着它将在线程池线程上运行。
除非您正在编写库代码,否则默认行为正是您所期望的。 WinForms,WPF和Silverlight(即所有UI框架)提供SynchronizationContext
,因此继续在UI线程上执行(并且可以安全地访问UI对象)。 ASP.NET还提供SynchronizationContext
,以确保继续在正确的请求上下文中执行。
其他线程(包括线程池线程,Thread
和BackgroundWorker
)不提供SynchronizationContext
。因此,默认情况下,控制台应用和Win32服务根本没有SynchronizationContext
。在这种情况下,continuation在线程池线程上执行。这就是为什么使用await
/ async
的控制台应用程序演示包括对Console.ReadLine
/ ReadKey
的调用或对Wait
执行阻止Task
的原因。
如果您发现自己需要SynchronizationContext
,则可以使用AsyncContext
库中的Nito.AsyncEx;它基本上只提供async
兼容的“主循环”和SynchronizationContext
。我发现它对Console应用程序和单元测试很有用(VS2012现在内置了对async Task
单元测试的支持)。
有关SynchronizationContext
的详情,请参阅my Feb MSDN article。
在任何时候都不是DoEvents
或等同的名字;相反,控制流一直返回,并且延续(函数的其余部分)计划在以后运行。这是一个更清洁的解决方案,因为它不会像使用DoEvents
时那样导致重入问题。
答案 1 :(得分:5)
async / await背后的整个想法是它很好地执行continuation传递,并且不为该操作分配新线程。继续可能在新线程上发生,可能在同一个线程上继续。
答案 2 :(得分:2)
async / await的真正“meat”(异步)部分通常是单独完成的,与调用者的通信是通过TaskCompletionSource完成的。正如这里所写http://blogs.msdn.com/b/pfxteam/archive/2009/06/02/9685804.aspx
TaskCompletionSource类型有两个相关的目的,它们的名称都提到:它是创建任务的源,也是该任务完成的源。实质上,TaskCompletionSource充当Task及其完成的生产者。
并且示例非常清楚:
public static Task<T> RunAsync<T>(Func<T> function)
{
if (function == null) throw new ArgumentNullException(“function”);
var tcs = new TaskCompletionSource<T>();
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
T result = function();
tcs.SetResult(result);
}
catch(Exception exc) { tcs.SetException(exc); }
});
return tcs.Task;
}
通过TaskCompletionSource
您可以访问可以等待的Task
对象,但不是通过您创建多线程的async / await关键字。
请注意,当许多“慢”函数转换为async / await语法时,您不需要非常使用TaskCompletionSource
。他们将在内部使用它(但最终某处必须有TaskCompletionSource
才能产生异步结果)
答案 3 :(得分:1)
我想解释它的方式是“await”关键字只是等待一个任务完成,但在等待时产生执行到调用线程。然后,它返回任务的结果,并在任务完成后从“await”关键字后的语句继续。
我注意到的一些人似乎认为Task与调用线程在同一个线程中运行,这是不正确的,可以通过尝试在等待调用的方法中更改Windows.Forms GUI元素来证明。但是,继续在尽可能在调用线程中运行。
它只是一种简洁的方式,无需在任务完成时拥有回调委托或事件处理程序。
答案 4 :(得分:1)
我觉得这个问题需要为人们提供一个简单的答案。因此,我将简化一下。
事实是,如果您保存任务而不等待它们,则异步/等待是“并发的”。
var a = await LongTask1(x);
var b = await LongTask2(y);
var c = ShortTask(a, b);
不是并发的。 LongTask1将在LongTask2启动之前完成。
var a = LongTask1(x);
var b = LongTask2(y);
var c = ShortTask(await a, await b);
是并发的。
虽然我还敦促人们加深了解并对此进行阅读,但是您可以使用async / await进行并发,这很简单。