当我可以使用.Result或不能使用.Result时,我仍在努力寻找解决方案。我一直在尝试通过完全“异步”使用异步来完全避免这种情况。
在我们的 ASP.NET 网络应用程序(非核心)中进行某些工作时,我遇到了这段代码。现在,该代码可以工作了(已经工作了两年),所以我知道它可以工作,我只是不知道为什么它可以工作。
这是通过同步控制器方法触发的。它是一连串的方法调用,经过几层,直到最终完成一些HTTP工作为止。我在这里简化了代码:
// Class1
public byte[] GetDocument1(string url)
{
return class2.GetDocument2(url).Result;
}
// Class2
public Task<byte[]> GetDocument2(string url)
{
return class3.GetDocument3(url)
}
// Class3
public async Task<byte[]> GetDocument3(string url)
{
var client = GetHttpClient(url);
var resp = client.GetAsync(url).Result;
if(resp.StatusCode == HttpStatusCode.OK)
{
using(var httpStream = await resp.Content.ReadAsStreamAsync())
{
// we are also using
await httpStream.ReadAsync(...);
}
}
}
据我所知,这一切开始时,我就处于“主ASP同步上下文”中(我从控制器中启动,最终到达此代码)。我们没有使用任何.ConfigureAwait(false)
,所以我相信我们总是会回到这种情况。
GetDocument3
中,为什么client.GetAsync(url).Result
没有死锁?GetDocument3
正在将.Result
与await
的内容混合在一起。总的来说,这是个好主意吗?是因为.Result
在等待之前来了吗?GetDocument1
中,为什么.Result
没有死锁?答案 0 :(得分:5)
client.GetAsync(url).Result
正在同步阻止。 resp.Content.ReadAsStreamAsync()
实际上没有等待。 Task
已经完成,因为HttpCompletionOption.ResponseContentRead
中使用了GetAsync
,所以这里的整个代码块都是伪装成异步的同步代码。
通常,永远不要使用.Result
或.Wait
或Task.Wait*
-如果绝对需要,请使用GetAwaiter().GetResult()
,它不会抛出{ {1}},您可能没有AggregateException
,但是像瘟疫一样应该避免。您完全可以使用catch
。
死锁只是在想要返回到原始线程(例如UI或ASP.NET)的上下文中是一个问题,并且该线程被同步等待阻塞。如果您始终在每个 async
上使用ConfigureAwait(false)
,除非 ,否则您实际上需要保留上下文(通常仅在顶部)级别的UI事件处理程序,因为您需要更新UI或ASP.NET控制器的顶层),那么您应该是安全的。
异步代码内部的同步阻塞也不是一件好事,但不会导致死锁。在要等待返回特定线程的上下文中,在同步等待中将await
与await
(默认)一起使用会导致死锁。