async / await返回callchain是如何工作的?

时间:2014-08-28 16:27:58

标签: c# .net asynchronous async-await task-parallel-library

我最近有一种情况,我有一个ASP.NET WebAPI控制器,需要在其action方法中对另一个REST服务执行两个Web请求。我编写了我的代码,将功能完全分离为单独的方法,看起来有点像这个例子:

public class FooController : ApiController
{

    public IHttpActionResult Post(string value)
    {
        var results = PerformWebRequests();
        // Do something else here...
    }

    private IEnumerable<string> PerformWebRequests()
    {
        var result1 = PerformWebRequest("service1/api/foo");
        var result = PerformWebRequest("service2/api/foo");

        return new string[] { result1, result2 };
    }

    private string PerformWebRequest(string api)
    {
        using (HttpClient client = new HttpClient())
        {
            // Call other web API and return value here...
        }
    }

}

因为我使用HttpClient所有网络请求都必须是异步的。我之前从未使用async / await,因此我开始天真地添加关键字。首先,我将async关键字添加到PerformWebRequest(string api)方法,但后来调用者抱怨PerformWebRequests()方法也必须async才能使用await。所以我做了async,但现在该方法的调用者也必须是async,依此类推。

我想知道的是兔子洞的距离必须标记async才能正常工作?肯定会有一些事情需要同步运行,在这种情况下如何安全处理?我已经读过,调用Task.Result是一个坏主意,因为它可能导致死锁。

2 个答案:

答案 0 :(得分:13)

  

我想知道的是兔子洞一定要走多远   标记为异步才能正常工作?肯定会有一个点   必须同步运行

不,不应该是任何同步运行的点,这就是异步的全部意义所在。短语&#34; async&#34; 实际上意味着一直向上调用堆栈。

当您异步处理邮件时,您会在真正的异步方法运行时让邮件循环处理请求,因为当您深入rabit漏洞时,There is no Thread

例如,当您有异步按钮时,单击事件处理程序:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    await DoWorkAsync();
    // Do more stuff here
}

private Task DoWorkAsync()
{
    return Task.Delay(2000); // Fake work.
}

单击该按钮时,将同步运行,直到达到第一个await。一旦命中,该方法将控制权返回给调用者,这意味着按钮事件处理程序将释放UI线程,这将释放消息循环以同时处理更多请求。

使用HttpClient同样如此。例如,当你有:

public async Task<IHttpActionResult> Post(string value)
{
    var results = await PerformWebRequests();
    // Do something else here...
}

private async Task<IEnumerable<string>> PerformWebRequests()
{
    var result1 = await PerformWebRequestAsync("service1/api/foo");
    var result = await PerformWebRequestAsync("service2/api/foo");

    return new string[] { result1, result2 };
}

private async string PerformWebRequestAsync(string api)
{
    using (HttpClient client = new HttpClient())
    {
        await client.GetAsync(api);
    }

    // More work..
}

了解async关键字如何一直上升到处理POST请求的main方法。这样,虽然异步http请求由网络设备驱动程序处理,但您的线程返回到ASP.NET ThreadPool并且可以同时处理更多请求。

控制台应用程序是一种特殊情况,因为当Main方法终止时,除非您旋转新的前台线程,否则应用程序将终止。在那里,您必须确保如果唯一的通话是异步通话,则您必须明确使用Task.WaitTask.Result。但在这种情况下,默认SynchronizationContextThreadPoolSynchronizationContext,其中没有机会导致死锁。

总而言之,异步方法不应该在堆栈顶部同步处理,除非存在异乎寻常的用例(例如控制台应用程序),它们应该异步流动所有允许线程在可能的情况下被释放的方式。

答案 1 :(得分:0)

你需要“一直异步”到调用堆栈的最顶层,在那里你可以到达一个可以处理所有异步请求的消息循环。