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.Wait
或Task.Result
。 但在这种情况下,默认的SynchronizationContext
是ThreadPoolSynchronizationContext
,其中没有机会导致死锁。
总而言之,异步方法不应该在堆栈顶部同步处理 ,除非有一个奇特的用例(例如控制台应用程序),它们应该异步流动,允许在可能的情况下释放线程。
您需要“一直异步”到调用堆栈的最顶层,在那里您可以到达可以处理所有异步请求的消息循环。