2012-11-27 133 views
4

我在玩C#的異步等待功能。當我在UI線程中使用它時,事情會按預期工作。但是,當我在非UI線程中使用它時,它無法按預期工作。考慮下面爲什麼在後臺線程不會等待任務完成?

private void Click_Button(object sender, RoutedEventArgs e) 
    { 
     var bg = new BackgroundWorker(); 
     bg.DoWork += BgDoWork; 
     bg.RunWorkerCompleted += BgOnRunWorkerCompleted; 
     bg.RunWorkerAsync(); 
    } 

    private void BgOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs) 
    { 
    } 

    private async void BgDoWork(object sender, DoWorkEventArgs doWorkEventArgs) 
    { 
     await Method(); 
    } 


    private static async Task Method() 
    { 
     for (int i = int.MinValue; i < int.MaxValue; i++) 
     { 
      var http = new HttpClient(); 
      var tsk = await http.GetAsync("http://www.ebay.com"); 
     } 
    } 

代碼當我執行該代碼,後臺線程不等待長時間運行的任務Method完成。在撥打Method後立即執行BgOnRunWorkerCompleted。爲什麼?我在這裏錯過了什麼?

P.S:我對這樣做的替代方式或正確方法不感興趣。我想知道這種情況下幕後發生的事情是什麼?爲什麼不等待?

+0

你會發現我的[異步/的await簡介](http://nitoprograms.blogspot.com/2012/02/async-and-await.html)很有幫助。 –

回答

7

所以,BgDoWorkBackgroundWorker

稱爲後臺線程,它調用Method,這將啓動循環,並呼籲http.GetAsync

GetAsync返回Task並繼續它在另一個線程的工作。

await其任務,因爲Task尚未完成,從Method

返回同樣,在BgDoWork返回另一個Task

所以的await的BackgroundWorker看到那BgDoWork返回,並假定它有完成。

然後,它提出了RunWorkerCompleted


基本上不async/await混合BackgroundWorker

+0

所以只有這樣才能做到這一點,將調用後臺線程中的等待,以確保它等待長時間運行的任務結束? –

+0

我無法在答案或評論中解釋'async/await' - 它太複雜了。有很多在網絡上的教程 - [這裏有一個MSDN上(http://msdn.microsoft.com/en-gb/library/vstudio/hh191443.aspx) –

+0

這不是BackgroundWorker的是如何工作的問題。根據文檔,你不能同步執行異步事件處理程序,因爲它們不能返回任務 –

1

我認爲你需要一些理由讓後臺線程在它等待Method()完成時保持活着狀態。有一個優秀的延續是不足以保持線程活着,所以你的後臺工作人員在Method()完成之前終止。

您可以通過更改代碼來證明這一點,以便後臺線程在await Method()之後執行Thread.Sleep。這幾乎肯定不是你想要的真實行爲,但如果線程睡了足夠長的時間,你會看到Method()完成。

1

以下是如何提出並處理DoWork。 (使用Reflector工具檢索代碼)。

private void WorkerThreadStart(object argument) 
{ 
    object result = null; 
    Exception error = null; 
    bool cancelled = false; 
    try 
    { 
     DoWorkEventArgs e = new DoWorkEventArgs(argument); 
     this.OnDoWork(e); 
     if (e.Cancel) 
     { 
      cancelled = true; 
     } 
     else 
     { 
      result = e.Result; 
     } 
    } 
    catch (Exception exception2) 
    { 
     error = exception2; 
    } 
    RunWorkerCompletedEventArgs arg = new RunWorkerCompletedEventArgs(result, error, cancelled); 
    this.asyncOperation.PostOperationCompleted(this.operationCompleted, arg); 
} 


protected virtual void OnDoWork(DoWorkEventArgs e) 
{ 
    DoWorkEventHandler handler = (DoWorkEventHandler) base.Events[doWorkKey]; 
    if (handler != null) 
    { 
     handler(this, e); 
    } 
} 

沒有特殊的處理,以等待異步方法。 (使用async/await關鍵字)。

要使其等待異步操作,需要進行以下更改。

async private void WorkerThreadStart(object argument) 

    await this.OnDoWork(e); 


async protected virtual void OnDoWork(DoWorkEventArgs e) 

    await handler(this, e); 

但隨後,BackgroundWorker是.NET 2.0構建體,和async/await是.NET 4.5。如果其中任何一個使用其他構造,它將是整圈。

+0

我不知道你爲什麼要將反編譯的'BackgroundWorker源代碼到這裏。 'DbDoWork()'是'async void'方法,無論如何,你都不能等待。 – svick

+0

意圖是顯示,異步/等待完整的鏈接需要等待。如果任何一個鏈接不在鏈中等待,調用方法不會等待異步方法完成。我有時遇到這個問題,這些通常在運行時遇到。通常Visual Studio會在這些情況下顯示警告。關於異步無效,我們仍然可以通過獲取基礎任務等待,然後等待任務。 – Tilak

+0

當你所擁有的只是一個'void'返回方法時,你會如何得到這個「潛在的任務」? – svick

2

基本上有兩個問題與您的代碼:

  1. BackgroundWorker沒有更新與async工作。而且方法async的全部重點是他們實際上第一次返回的東西還沒有完成,而不是阻塞。所以,當你的方法返回(一個await後),BackgroundWorker認爲已經完成,並提出RunWorkerCompleted
  2. BgDoWork()async void方法。這樣的方法是「火和忘記」,你不能等待他們完成。因此,如果您運行的方法可以理解爲async,那麼您還需要將其更改爲async Task方法。

你說你是不是在找替代品,但我認爲它可以幫助你理解這個問題,如果我提供了一個。假設BgDoWork()應該在後臺線程運行,BgOnRunWorkerCompleted()應該跑回來在UI線程上,你可以使用這樣的代碼:

private async void Click_Button(object sender, RoutedEventArgs e) 
{ 
    await Task.Run((Func<Task>)BgDoWork); 
    BgOnRunWorkerCompleted(); 
} 

private void BgOnRunWorkerCompleted() 
{ 
} 

private async Task BgDoWork() 
{ 
    await Method(); 
} 

這裏,Task.Run()作品作爲async知曉的替代BackgroundWorker(它運行的方法在後臺線程上並返回一個Task,可用於等待它實際完成)。在Click_Button()之後的await之後,您又回到了UI線程,這就是BgOnRunWorkerCompleted()將運行的位置。 Click_Button()是一個async void方法,這幾乎是唯一一種您想要使用它的情況:在事件處理程序方法中,您不需要等待。

1

你不能等待一個事件處理程序,因爲它不返回任何東西等待上。從async關鍵字的文檔:

空隙返回類型主要用於定義事件處理程序,其中需要void返回類型。 void返回異步方法的調用者無法等待它,並且無法捕獲該方法拋出的異常。

通過向BgDoWork事件處理程序添加async關鍵字,指示.NET異步執行處理程序,並在遇到第一個yield操作時立即返回。在這種情況下,出現這種情況的第一次調用http.GetAsync後