async一直向下问题

时间:2014-01-12 07:08:38

标签: c# .net asp.net-mvc async-await

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

public async Task<ActionResult> Index(int[] ids)
{
    List<int> listOfDataPoints = dataPointService(ids);
    List<Task> dpTaskList = new List<Task>();
    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方法之间有更多异步方法。

2 个答案:

答案 0 :(得分:4)

是。有一个惩罚(虽然不是一个巨大的),如果你不需要async不是。这种模式通常被称为&#34;返回等待&#34;你几乎总能删除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已在同步调用中运行并完成。

答案 1 :(得分:2)

你必须小心线程消息泵和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

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

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

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

在我的Core I7上,每次调用都会得到2微秒的开销(注释掉Debug.Print行),并且WPF应用程序中的500万次调用的内存消耗为6,5GB,这会给你带来130KB的内存开销异步操作链。如果您追求高可扩展性,则需要在GC之后观看。在Joe Duffy has finished his new language之前我们必须使用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<string> GetSlowString()
    {
        Print("Before");

        string otherResult = await OtherTask();

        return "GetSlow" + otherResult + await GetVerySlow(); ;
    }

    private Task<string> OtherTask()
    {
        return Task.Run(() =>
        {
            Print("Sleeping for 2s");
            Thread.Sleep(2 * 1000);
            Print("Sleeping done");
            return "OtherTaskResult";
        });
    }

    private Task<string> GetVerySlow()
    {
        Print("Inside");
        return Task.Run(() =>
        {
            Print("Sleeping 3s");
            Thread.Sleep(3000);
            Print("Sleeping Done");
            return "GetVerySlowReturn";
        });
    }
}