为什么对.Result的调用不会引发死锁?

eme*_*oel 0 c# asp.net asp.net-mvc async-await

当我可以使用.Result以及不能使用.Result时,我仍在努力寻找解决方案。我一直在尝试通过完全“使用异步”来完全避免这种情况。

在我们的ASP.NET Web应用程序(非核心)中进行一些工作时,我遇到了这段代码。现在,该代码可以工作了(已经工作了两年),所以我知道它可以工作,我只是不知道为什么它可以工作。

这是从同步控制器方法触发的。它是一连串的方法调用,经过几层,直到最终完成一些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(...);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

据我所知,这一切开始时,我就在“主ASP同步上下文”上(我从一个控制器开始,最终到达此代码)。我们没有使用任何.ConfigureAwait(false),所以我相信我们总是在回到这种情况。

  1. 在中GetDocument3,为什么不client.GetAsync(url).Result死锁?
  2. GetDocument3.Resultawait东西混合。总的来说,这是个好主意吗?在这里.Result等待就可以了吗?
  3. 在中GetDocument1,为什么不.Result死锁?

Geo*_*yar 5

client.GetAsync(url).Result正在同步阻止。resp.Content.ReadAsStreamAsync()实际上没有做任何等待。在Task已经完成,因为HttpCompletionOption.ResponseContentRead使用在GetAsync,所以整个代码块这里是同步代码假装是异步的。

通常,您永远不应该使用.Result或- .Wait或者Task.Wait*,如果绝对必须使用,请使用GetAwaiter().GetResult(),它不会抛出包裹在AggregateExceptions中的异常,对此您可能没有catch,但即使那样,也应避免瘟疫。您async一路向下使用是正确的。

死锁仅是要返回到原始线程(例如UI或ASP.NET)的上下文中的问题,并且该线程被同步等待阻塞。如果您始终ConfigureAwait(false)每一个上 使用await除非您知道您实际上需要在延续上保留上下文(通常仅在顶层UI事件处理程序中保留,因为您需要在UI或ASP.NET的顶层进行更新管理员),那么您应该是安全的。

异步代码内部的同步阻塞也不是一件好事,但不会导致死锁。在要返回到特定线程的上下文中,在同步等待中使用awaitwith ConfigureAwait(true)(默认设置)将导致死锁。