async一直向下问题

我有一个异步asp.net控制器。 该控制器调用异步方法。 实际执行异步IO工作的方法深入到我的应用程序中。 控制器和链中最后一个方法之间的一系列方法都用async修饰符标记。 以下是我如何设置代码的示例:

public async Task Index(int[] ids) { List listOfDataPoints = dataPointService(ids); List dpTaskList = new List(); foreach (var x in listOfDataPoints) { dpTaskList.Add(C_Async(x)); } await Task.WhenAll(dpTaskList); return View(); } private async Task C_Async(int id) { //this method executes very fast var idTemp = paddID(id); await D_Async(idTemp); } private async Task D_Async(string id) { //this method executes very fast await E_Async(id); } private async Task E_Async(string url) { //this method performs the actual async IO result = await new WebClient().DownloadStringTaskAsync(new Uri(url)) saveContent(result); } 

如您所见,控制器异步调用C_Async(x),然后有一系列异步方法到E_Async。 控制器和E_Async之间有方法,并且都有async修饰符。 是否存在性能损失,因为有些方法使用异步修改器但没有执行任何异步IO工作?

注意:这是实际代码的简化版本,控制器和E_Async方法之间有更多异步方法。

是。 有一个惩罚(虽然不是一个巨大的),如果你不需要async不要。 这种模式通常称为“返回等待”,您几乎总能删除asyncawait 。 只需返回代表异步操作的已有任务:

 private Task C_Async(int id) { // This method executes very fast var idTemp = paddID(id); return D_Async(idTemp); } private Task D_Async(string id) { // This method executes very fast return E_Async(id); } 

在此特定情况下, Index将仅await E_Async返回的任务。 这意味着在完成所有I/O之后,下一行代码将直接return View();C_AsyncD_Async已在同步调用中运行并完成。

你必须小心线程消息泵和async真正做的事情。 下面的示例调用异步方法,该方法调用另外两个异步方法,这两个方法启动两个任务来完成等待2和3秒的实际工作。

 13.00 6520 .ctor Calling async method 13.00 6520 RunSomethingAsync Before 13.00 6520 GetSlowString Before 13.00 5628 OtherTask Sleeping for 2s 15.00 5628 OtherTask Sleeping done 15.00 6520 GetVerySlow Inside 15.00 2176 GetVerySlow Sleeping 3s 18.00 2176 GetVerySlow Sleeping Done 18.00 6520 RunSomethingAsync After GetSlowOtherTaskResultGetVerySlowReturn 

正如您所看到的那样,序列化的呼叫可能不是您在演出后所需的呼叫。 也许两个不​​同的等待调用不相互依赖,可以直接作为任务启动。

直到GetSlowStringBefore的所有方法都在启动异步操作的UI或ASP.NET线程上调用(如果它有消息泵)。 只有具有操作结果的最后一次调用被编组回发起线程。

性能损失在ContextSwitch区域中的某处唤醒已经存在的线程。 这应该是微秒级别的某个地方。 最昂贵的东西是创建托管对象和垃圾收集器清理临时对象。 如果你在紧密循环中调用它,那么你将受GC限制,因为有一个上限可以创建多少个线程。 在这种情况下,TPL将在需要内存分配的队列中缓冲您的任务,然后从线程池中释放具有n个工作线程的队列。

在我的Core I7上,每次调用都会得到2微秒的开销(注释掉Debug.Print行),WPF应用程序中500万次调用的内存消耗为6.5GB,每个异步操作链的内存开销为130KB 。 如果您追求高可扩展性,则需要在GC之后观看。 在Joe Duffy完成他的新语言之前,我们必须使用我们目前拥有的CLR。

 public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); Print("Calling async method"); RunSomethingAsync(); } private async void RunSomethingAsync() { Print("Before"); string msg = await GetSlowString(); Print("After " + msg); cLabel.Content = msg; } void Print(string message, [CallerMemberName] string method = "") { Debug.Print("{0:N2} {1} {2} {3}", DateTime.Now.Second, AppDomain.GetCurrentThreadId(), method, message); } private async Task GetSlowString() { Print("Before"); string otherResult = await OtherTask(); return "GetSlow" + otherResult + await GetVerySlow(); ; } private Task OtherTask() { return Task.Run(() => { Print("Sleeping for 2s"); Thread.Sleep(2 * 1000); Print("Sleeping done"); return "OtherTaskResult"; }); } private Task GetVerySlow() { Print("Inside"); return Task.Run(() => { Print("Sleeping 3s"); Thread.Sleep(3000); Print("Sleeping Done"); return "GetVerySlowReturn"; }); } }