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

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

public class FooController : ApiController { public IHttpActionResult Post(string value) { var results = PerformWebRequests(); // Do something else here... } private IEnumerable 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所有Web请求都必须是异步的。 我以前从未使用async / await,因此我开始天真地添加关键字。 首先,我将async关键字添加到PerformWebRequest(string api)方法,但然后调用者抱怨PerformWebRequests()方法也必须是async的才能使用await 。 所以我做了那个async但现在该方法的调用者也必须是async ,依此类推。

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

我想知道的是兔子洞的距离是否必须标记为同步才能正常工作? 肯定会有一些东西必须同步运行

不,不应该有任何同步运行的点,这就是异步的全部意义。 短语“async all a way”实际上意味着调用堆栈的所有方式

当您异步处理消息时,您正在让您的消息循环处理请求,同时您的真正异步方法运行,因为当您深入rabit漏洞时, 没有线程

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

 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 Post(string value) { var results = await PerformWebRequests(); // Do something else here... } private async Task> 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 ,其中没有机会导致死锁。

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

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