2017-07-06 127 views
0

我想更好地瞭解如何從異步操作更新Windows窗體進度欄,但我得到了一些意外的行爲。從異步方法更新進度欄

基本上我正在點擊一個按鈕來更新進度條,然後在進度條100%更新後將其設回0。

這是我的代碼:

private async void button1_Click(object sender, EventArgs e) 
    { 
     await CallMethodAsync().ContinueWith((prevTask) => 
     { 
      prevTask.Wait(); 
      progressBar1.Invoke(new Action(() => { progressBar1.Value = 0; })); 
     }); 
    } 

    private static async Task ExecuteMethodAsync(IProgress<double> progress = null) 
    { 
     double percentComplete = 0; 
     bool done = false; 

     while (!done) 
     { 
      if (progress != null) 
      { 
       progress.Report(percentComplete); 
      } 

      percentComplete += 10; 

      if(percentComplete == 100) 
      { 
       done = true; 
      } 
     } 
    } 

    private async Task CallMethodAsync() 
    { 
     var progress = new Progress<double>(); 
     progress.ProgressChanged += (sender, args) => { progressBar1.Increment(10); }; 
     await ExecuteMethodAsync(progress); 
    } 

有了這個實施進度條並不是在所有的,即使我把更新的「等待()」上應該更新進度條的值的操作。

如果我刪除這部分代碼:

progressBar1.Invoke(new Action(() => { progressBar1.Value = 0; })); 

進度條被更新,但它仍然是所有的時間這樣的,我想將其設置回0,一旦它被完全充滿,這樣我當我再次點擊按鈕時,可以再次更新它。

有人可以請解釋我做錯了什麼?

+1

你應該得到一個編譯器警告*「CS1998 C#這個異步方法缺少'await'操作符,並且會同步運行,考慮使用'await'操作符來等待非阻塞API調用或'await Task.Run(.. )'在後臺線程上執行CPU綁定的工作。「*。你做錯的事情是導致編譯器警告,並且警告甚至會告訴你如何解決它。 –

+1

此外,你不應該混合[ContinueWith和異步](https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html)(但這不會導致你的問題) –

+1

檢出https ://stackoverflow.com/questions/11500563/winform-multithreading-use-backgroundworker-or-not –

回答

3

發明了異步等待語法的原因之一是因爲當使用諸如ContinueWith之類的函數連接任務時很難遵循指令序列。

如果使用異步等待,很少有必要使用像ContinueWith這樣的語句。在await之後,線程在await之後已經繼續執行語句。

如果點擊該按鈕,則要撥打ExcecuteMethodAsync。此功能需要IProgress,因爲它想定期報告進度。你想異步地調用這個函數,所以無論函數什麼時候需要等待什麼,它都不會真的等待,但是將控制返回給你,這樣你就可以做其他的事情而不是真的在等待,直到遇到await,在這種情況下你的呼叫者繼續處理,直到他遇到等待,等等。

異步等待的好處在於,在調用異步函數後繼續的線程與調用線程具有相同的上下文。這意味着您可以將其視爲您的原創主題。沒有InvokeRequired,沒必要和互斥等來保護數據

如下您的函數可以簡化爲:

async Task CallMethodAsync() 
{ 
    var progress = new Progress<double>(); 
    progress.ProgressChanged += OnProgressReported; 

    await ExecuteMethodAsync(progress); 
} 

private void OnProgressReported(object sender, ...) 
{ 
    // because this thread has the context of the main thread no InvokeRequired! 
    this.progressBar1.Increment(...); 
} 

private async void button1_Click(object sender, EventArgs e) 
{ 
    await CallMethodAsync(); 
} 

所以單擊該按鈕時,CallMethodAsync被調用。此功能將創建一個Progress對象並訂閱其Report事件。請注意,這仍然是你的UI線程。然後它調用ExecuteMethodAsync,它將定期引發由OnProgressReported處理的事件報告。

因爲ExecuteMethodAsync是異步的,所以你可以確定有某處等待着它。這意味着無論什麼時候它必須等待,控制權返回給調用者,即CallMethodAsync,直到遇到await,在這種情況下是立即。

控制進入調用堆棧向上給呼叫者,這是的button1_Click,在那裏它立即遇到AWAIT,所以控制進入到調用堆棧等

所有這些控件具有相同的上下文:它是作爲如果它們是相同的線程。

的文章,對我幫助很大,瞭解異步的await是中間爲異步this interview with Eric Lippert.搜索某個地方等待

另一個articel是幫了我很多學習好的做法是this article by the ever so helpful Stephen ClearyAsync/Await - Best Practices in Asynchronous Programming也由斯蒂芬·克利