[英]Async/Await - is it *concurrent*?
我一直在考虑 C# 5 中的新异步内容,但出现了一个特定问题。
我知道await
关键字是一个巧妙的编译器技巧/语法糖来实现继续传递,其中方法的其余部分被分解为Task
对象并排队等待按顺序运行,但控制权返回给调用方法.
我的问题是我听说目前这一切都在一个线程上。 这是否意味着这种异步的东西真的只是一种将延续代码转换为Task
对象,然后在每个任务完成后调用Application.DoEvents()
然后开始下一个任务的方法?
或者我错过了什么? (问题的这一部分是修辞性的 - 我完全知道我错过了一些东西:))
它是并发的,从某种意义上说,许多未完成的异步操作可能随时在进行中。 它可能是也可能不是多线程的。
默认情况下, await
会将继续安排回“当前执行上下文”。 “当前执行上下文”被定义为SynchronizationContext.Current
如果它是非null
或TaskScheduler.Current
如果没有SynchronizationContext
。
您可以通过调用ConfigureAwait
并为continueOnCapturedContext
参数传递false
来覆盖此默认行为。 在这种情况下,延续将不会被调度回该执行上下文。 这通常意味着它将在线程池线程上运行。
除非您正在编写库代码,否则默认行为正是您想要的。 WinForms、WPF 和 Silverlight(即所有 UI 框架)提供SynchronizationContext
,因此延续在 UI 线程上执行(并且可以安全地访问 UI 对象)。 ASP.NET 还提供了一个SynchronizationContext
来确保继续在正确的请求上下文中执行。
其他线程(包括线程池线程、 Thread
和BackgroundWorker
)不提供SynchronizationContext
。 因此,默认情况下控制台应用程序和 Win32 服务根本没有SynchronizationContext
。 在这种情况下,延续在线程池线程上执行。 这就是为什么使用await
/ async
控制台应用程序演示包括对Console.ReadLine
/ ReadKey
的调用或对Task
执行阻塞Wait
。
如果您发现自己需要SynchronizationContext
,您可以使用我的Nito.AsyncEx库中的AsyncContext
; 它基本上只是提供了一个async
兼容的“主循环”和一个SynchronizationContext
。 我发现它对控制台应用程序和单元测试很有用(VS2012 现在内置了对async Task
单元测试的支持)。
有关SynchronizationContext
更多信息,请参阅我的二月 MSDN 文章。
在任何时候都不会调用DoEvents
或等效的方法; 相反,控制流一直返回,并且延续(函数的其余部分)被安排在稍后运行。 这是一个更简洁的解决方案,因为它不会像使用DoEvents
那样导致重入问题。
async/await 背后的整个想法是它可以很好地执行继续传递,并且不会为操作分配新线程。 延续可能发生在新线程上,也可能在同一线程上继续。
async/await 真正的“肉”(异步)部分通常是单独完成的,与调用者的通信是通过 TaskCompletionSource 完成的。 正如这里写的http://blogs.msdn.com/b/pfxteam/archive/2009/06/02/9685804.aspx
TaskCompletionSource 类型有两个相关的目的,这两个目的都由它的名字暗示:它是创建任务的源,以及任务完成的源。 本质上,TaskCompletionSource 充当任务及其完成的生产者。
这个例子很清楚:
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
才能获得异步结果)
我觉得这个问题需要一个更简单的答案。 所以我要过分简化。
事实是,如果您保存任务并且不等待它们,则 async/await 是“并发的”。
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 进行并发,而且非常简单。
我喜欢解释它的方式是“await”关键字只是等待任务完成,但在等待时将执行交给调用线程。 然后它返回任务的结果,并在任务完成后从“await”关键字之后的语句继续。
我注意到的一些人似乎认为 Task 与调用线程在同一线程中运行,这是不正确的,可以通过尝试更改等待调用的方法中的 Windows.Forms GUI 元素来证明。 但是,只要可能,continuation 就会在调用线程中运行。
它只是一种在任务完成时不必具有回调委托或事件处理程序的巧妙方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.