11

我想首先把代碼,然後說明情況,並要求根據我的問題:異步/等待與ConfigureAwait的continueOnCapturedContext參數的SynchronizationContext用於異步連續

public partial class MainWindow : Window { 

    public MainWindow() { 
     InitializeComponent(); 
    } 

    private async void Button_Click_2(object sender, RoutedEventArgs e) { 

     var result = await GetValuesAsync(); 
     Foo.Text += result; 
    } 

    public async Task<string> GetValuesAsync() {   

     using (var httpClient = new HttpClient()) { 

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


      // This is the continuation for the httpClient.GetAsync method. 
      // We shouldn't get back to sync context here 
      // Cuz the continueOnCapturedContext is set to *false* 
      // for the Task which is returned from httpClient.GetAsync method 
      var html = await GetStringAsync(); 

      // This is the continuation for the GetStringAsync method. 
      // Should I get back to sync context here? 
      // Cuz the continueOnCapturedContext is set to *true* 
      // for the Task which is returned from GetStringAsync 

      // However, GetStringAsync may be executed in another thread 
      // which has no knowledge for the sync context 
      // because the continueOnCapturedContext is set to *false* 
      // for the Task which is returned from httpClient.GetAsync method. 

      // But, on the other hand, GetStringAsync method also has a 
      // chance to be executed in the UI thread but we shouldn't be 
      // relying on that. 
      html += "Hey..."; 
      Foo.Text = html; 

      return html; 
     } 
    } 

    public async Task<string> GetStringAsync() { 

     await Task.Delay(1000); 
     return "Done..."; 
    } 
} 

這是一個相當簡單的WPF樣品,其運行在.NET 4.5上,可能沒有多大意義,但這應該有助於我解釋我的情況。

我有一個按鈕,在屏幕上有一個異步點擊事件。當您查看GetValuesAsync代碼時,您會看到await關鍵字的兩次使用情況。隨着第一次使用,我將Task.ConfigureAwait方法的continueOnCapturedContext參數設置爲false。所以,這表明我不需要我的繼續在SynchronizationContext.Current內執行。到現在爲止還挺好。

在第二個await的用法(與GetStringAsync方法),我沒有撥打ConfigureAwait方法。所以,我基本上表明,我想想要回到當前的同步上下文爲繼續GetStringAsync方法。所以,正如你所看到的,我嘗試在延續中設置TextBlock.Text(屬於UI線程)屬性。

當我運行應用程序,並單擊按鈕,我得到一個異常給我下面的消息:因爲不同的 線程擁有它

調用線程不能訪問該對象。

起初,這沒有任何意義,我和我想我發現了一個錯誤,但那時,我意識到GetStringAsync可能在另一個線程(很有可能),這是比UI線程不同,並沒有被執行因爲對於從httpClient.GetAsync方法返回的TaskcontinueOnCapturedContext設置爲false

這是這裏的情況嗎?此外,在這種情況下,是否有機會將GetStringAsync方法回發給UI線程bacuse httpClient.GetAsync方法的延續可能會在UI線程內執行?

我在代碼裏面也有一些註釋。鑑於我的問題和代碼中的評論,我在這裏錯過了什麼?

+0

此答案有幫助嗎? http://stackoverflow.com/a/12357113/171121 –

回答

11

當你調用ConfigureAwait(false),該方法的其餘部分將在一個線程池線程執行,除非Taskawait ING已經完成。

由於GetAsync幾乎肯定會異步運行,我預計GetStringAsync在線程池線程上運行。

public async Task<string> GetValuesAsync() {   

    using (var httpClient = new HttpClient()) { 

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

     // And now we're on the thread pool thread. 

     // This "await" will capture the current SynchronizationContext... 
     var html = await GetStringAsync(); 
     // ... and resume it here. 

     // But it's not the UI SynchronizationContext. 
     // It's the ThreadPool SynchronizationContext. 
     // So we're back on a thread pool thread here. 

     // So this will raise an exception. 
     html += "Hey..."; 
     Foo.Text = html; 

     return html; 
    } 
} 

而且,在這種情況下,是否有被張貼GetStringAsync方法回到UI線程bacuse的httpClient.GetAsync方法可以繼續在UI線程內執行的機會呢?

的唯一途徑GetStringAsync將在UI線程上運行是,如果GetAsync完成它實際上是await版之前。 高度不太可能。

出於這個原因,我更喜歡使用ConfigureAwait(false)await一旦環境不再需要。

+0

謝謝!你最後一句話是我認爲遵循的方式(我一直在,但現在我要堅持更多)。這不會有明顯的區別,但使用'ConfigureAwait(false)'也可以節省我們不必要的'SynchronizationContext'檢查是否不需要。 – tugberk

+0

當然,「極不可能」是不夠的。要麼它必須是100%,要麼你的代碼需要容忍這兩種情況。 – usr

+1

我同意一般。這就是爲什麼當不再需要上下文時,我更願意爲每個'await'使用'ConfigureAwait(false)'。 –