2017-07-17 81 views
1

我嘗試使用async/await調用運行某個操作時更新進度條,但UI正在更新時凍結。當進度條得到更新時,UI被阻塞

基本上這是一個非常簡單的需求,當使用BackgroundWorker時,但現在使用這個async/await調用事情似乎變得有點複雜。

首先,async/await的用法可以像BackgroundWorker一樣填充進度條嗎?這兩種方法有什麼共同點?

在我的用戶界面我有一個進度條和一個按鈕,當點擊按鈕時,進度條開始更新,但UI凍結,這應該不會發生,因爲async/await應該以「併發」方式工作。 如果我使用BackgroundWorked來完成此操作,UI不會凍結。

有人可以請我解釋一下我做錯了什麼,以及如何修改下面的代碼,以便在進度條更新時保持UI響應? 當更新進度條時,async/await的用法可以像BackgroundWorker那樣工作嗎?

下面是我的代碼:

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

private async Task CallMethodAsync() 
{ 
    this.progressBar1.Value = 0; 
    this.progressBar1.Maximum = 1000000; 
    var progressbar1 = new Progress<double>(); 
    progressbar1.ProgressChanged += Progressbar1_ProgressChanged; 

    await ExecuteMethodAsync(progressbar1); 
} 

private async Task ExecuteMethodAsync(IProgress<double> progress = null) 
{ 
    await Task.Run(new Action(() => { 

     double percentComplete = 0; 
     bool done = false; 

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

      percentComplete += 1; 

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

private void Progressbar1_ProgressChanged(object sender, double e) 
{ 
    this.progressBar1.Increment(1); 
} 
+1

不要等待更新欄的任務,並使用'this.Invoke(new Action()....'來更新UI。 – Crowcoder

+0

Ignore @ Crowcoder的評論。您的代碼沒問題。等待並使用'Progress 「'實際上是這種情況的現代成語,你的代碼中的問題是你沒有做任何實際的工作,你只是用更新發送UI線程,並且它跟不上。例如,將迭代計數改爲100而不是1000000,並在你的while循環中添加一個await Task.Delay(500);你不但可以擺脫await Task.Run( )'(即循環可以在你的'ExecuteMethodAsync()'方法中,而不是lambda),它會像你想要的那樣工作。 –

回答

1

您的代碼基本上是好的。唯一的問題是它沒有做任何實際的工作,所以它花費所有的時間來嘗試更新UI。在我的計算機上,整個事情在幾秒鐘內完成,但由於UI非常忙於處理進度更新,因此幾乎沒有時間用於其他用戶交互。例如,如果我拖動窗口,窗口位置將在任務循環執行期間更新一次或兩次。

在現實世界中,您可以在主要工作塊之間更新頻率更低的頻率。您可以在代碼中更好地模擬此代碼,方法是在循環外部清除Task.Run(),並在循環內使用Task.Delay()來表示您的長期工作。在現實世界中,您可能會用Task.Run()替換Task.Delay()以執行個人工作組件。

當然,如果你這樣做,你不再需要Progress<T>類。這是一個很有用的類,但是通常在使用async/await時,工作和進度更新可以交替進行,從而允許您使用await而不必經過Progress<T>才能返回到UI線程以獲取進度更新。如果你改變你的代碼示例作爲我的建議,它會看起來更象這樣:

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

private async Task CallMethodAsync() 
{ 
    this.progressBar1.Value = 0; 
    this.progressBar1.Maximum = 1000; 

    await ExecuteMethodAsync(); 
} 

private async Task ExecuteMethodAsync() 
{ 
    for (int percentComplete = 0; percentComplete < 1000; percentComplete++) 
    { 
     await Task.Delay(10); 
     progressBar1.Increment(1); 
    } 
} 

Progressbar1_ProgressChanged()方法甚至沒有必要,而且代碼的其餘部分得到了很多簡單。因爲我使用await,我可以使用progressBar1對象,而不是直接使用跨線程調用機制,像Progress<T>

注意,在上面的,我只等待10毫秒。這是關於Windows線程調度程序的限制—它不能精確地比線程更頻繁地調度線程,但是這仍然足以讓UI線程跟上更新的速度。一個現實世界的情景可能會有大量的工作比這更長,從100毫秒起。

即使有這種相對較高的更新頻率,UI拖動窗口等UI交互也會順利進行。