等待和僵局预防 - 澄清?

时间:2014-12-24 12:20:09

标签: c# .net task-parallel-library async-await

我阅读this article有关Task.ConfigureAwait的内容,这有助于防止异步代码死锁。

看看这段代码:(我知道我不应该.Result,但它是问题的一部分

private void Button_Click(object sender, RoutedEventArgs e)
{
    string result = GetPageStatus().Result;
    Textbox.Text = result;
}
public async Task<string> GetPageStatus()
{
    using (var httpClient = new HttpClient())
    {
        var response = await httpClient.GetAsync("http://www.google.com");
        return response.StatusCode.ToString();
    }
}

这将导致死锁,因为:

  1. .Result - 操作将在等待异步操作完成时阻止当前线程(即UI线程)。

  2. 网络调用完成后,将尝试继续对捕获的上下文执行response.StatusCode.ToString() - 方法。 (被封锁 - 因此死锁)。

  3. 一种解决方案是使用:

    var response = await httpClient.GetAsync("http://www.google.com").ConfigureAwait(false);

    但是其他解决方案是一直异步(没有阻塞):

    /*1*/   private async void Button_Click(object sender, RoutedEventArgs e)
    /*2*/   {
    /*3*/       string result = await GetPageStatus();
    /*4*/       Textbox.Text = result;
    /*5*/   }
    /*6*/   public async Task<string> GetPageStatus()
    /*7*/   {
    /*8*/       using (var httpClient = new HttpClient())
    /*9*/       {
    /*10*/          var response = await httpClient.GetAsync("http://www.google.com");
    /*11*/          return response.StatusCode.ToString();
    /*12*/      }
    /*13*/   }
    

    问题:

    (我试图了解此代码如何帮助解决问题 - 通过上下文POV)。

    1. #3和行#10是否会捕获不同的上下文?

    2. 我认为对于流动的方式我是对的:

      • 第3行调用#6(调用#10)并看到它还没有完成,所以等待(#3 = UI线程的捕获上下文)。

      • 稍后,第10行会在完成后捕获另一个上下文(我将其称为newContext ),它将返回&#34; newContext&#34;然后释放UI上下文(线程)。

    3. 我是对的吗?

      1. 可视化:(这是正确的流程吗?)
      2. Code execution flow

3 个答案:

答案 0 :(得分:4)

  

第3行和第10行是否捕获不同的上下文?

正如你的代码看起来那样,没有。它们都将捕获相同的UI同步上下文,因为您不使用ConfigureAwait(false)来阻止将连续编组回UI环境。

  

我认为对流动的方式是正确的:

     

第3行调用#6(调用#10)并看到它还没有完成,   所以它等待(捕获#3 = UI线程的上下文)。

     

稍后,第10行捕获另一个上下文(我将称之为   newContext)完成之后,它回到&#34; newContext&#34;然后   发布UI上下文(线程)。

几乎。没有&#34;新的背景&#34;正在通话中创建。它始终是相同的UI同步上下文。例如,如果你有两个异步调用,一个使用ConfigureAwait(false),第二个调用将继续在线程池线程上执行。

对于可视化,它确实可以正确捕获代码的执行流程。

答案 1 :(得分:3)

没有不同的背景。在这两种情况下,SyncrhonizationContext都是单线程UI同步上下文(只要您不使用ConfigureAwait(false)

当UI线程同步等待自身时发生死锁。您可以通过避免使用ConfigureAwait(false)的UI线程或通过避免Task.Result来阻止它来解决这个问题。

“一路async 解决死锁的原因是UI线程不再被阻止,可以自由运行两个async操作的延续。

所以:

  1. 不,它是相同的SyncrhonizationContext
  2. 不,在完成#11任务完成GetAsync任务后,行GetPageStatus将在UI线程(未被阻止)上恢复。然后行#4也将在UI线程上恢复。
  3. 重要的是要理解在这种情况下SynchronizationContext只能确保专用线程(串行)完成一些工作。只要线程可以自由执行该工作单元,就永远不会发生死锁。

答案 2 :(得分:0)

我假设这是WinForms/WPF/Asp.NET应用程序,因此在第3行中将捕获UI线程同步上下文,并且任务将在线程池线程中运行。因此,将从线程池线程调用第10行,并使用默认调度程序。