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