2017-07-17 19 views
1

async/await呼び出しを使用して操作が実行されてもプログレスバーを更新しようとしていますが、進行状況バーが更新されている間UIがフリーズします。進行状況バーの更新中にUIがブロックされる

これは基本的にBackgroundWorkerを使用したときの非常に簡単な要件でしたが、現在この非同期/待機待ちの呼び出しを使用すると少し複雑に思えます。

まず、BackgroundWorkerのように非同期/待機使用がプログレスバーを埋めることができますか?これらの2つのアプローチの間に何か共通点がありますか?

私のUIには、プログレスバーとボタンがあります。ボタンがクリックされるとプログレスバーが更新され始めますが、UIはフリーズし、非同期/待機が「同時実行」方法。 私が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

バーを更新し、UIを更新するために 'this.Invoke(new Action()....'を使用しないでください。 – Crowcoder

+0

@ Crowcoderのコメントを無視してください。 は実際にはこのシナリオの現代的なイディオムです。あなたのコードの問題は、あなたが何か_actual_仕事をしていないということです。あなたはアップデートでUIスレッドをスパムしているだけです。例えば、 '1000000'ではなく' 100'に反復回数を変更し、 'while'ループに' await Task.Delay(500); 'を追加すれば、' await Task.Run( ) '(つまり、ループはラムダの代わりに' ExecuteMethodAsync() 'メソッド内にあることができます)、望むように動作します。 –

答えて

1

あなたのコードは基本的には大丈夫です。唯一の問題は、実際の作業を行わないということです。そのため、UIを更新しようとするすべての時間を費やしています。私のコンピュータでは、数秒ですべてが完了しますが、UIが進捗状況から更新を処理するために忙しいので、他のユーザーとのやりとりにほとんど時間がありません。たとえば、ウィンドウをドラッグすると、ウィンドウの位置は、タスクループが実行されている間に1〜2回だけ更新されます。

現実のシナリオでは、主要な作業ブロック間で、進捗状況の更新頻度が低くなります。ループ内でTask.Run()を取り除き、ループ内でTask.Delay()を使用して長時間実行される作業を表現することで、これをよりよくシミュレートできます。現実のシナリオでは、をTask.Run()に置き換えて個の個別の作業コンポーネントを実行することがあります。

もちろん、このようにすれば、Progress<T>クラスは必要なくなります。便利なクラスですが、しばしばasync/awaitを使用すると、作業と進捗の更新が交互になり、Progress<T>を経由する代わりに、awaitを使用して進行状況の更新の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を使用しているので、私の代わりに、上記では、私はわずか10ミリ秒待っていることProgress<T>

注様クロススレッド呼び出しメカニズムを使用する直接progressBar1オブジェクトを使用することができます。これは、Windowsスレッドスケジューラ—の限界です。スレッドを正確にスケジュールすることはできません。—よりも頻繁にスレッドをスケジュールすることはできませんが、UIスレッドが更新に対応できるほど遅いです。実世界のシナリオでは、100ミリ秒以上の長時間の作業が必要です。

この更新頻度が比較的高い場合でも、ウィンドウをドラッグするなどのUIのやりとりはスムーズに行われます。

関連する問題