チュートリアル : バックグラウンド操作を使用するフォームの実装
更新 : 2007 年 11 月
完了するまで時間がかかる操作があり、操作中にユーザー インターフェイス (UI) が応答を停止 (つまり "ハングアップ") しないようにするには、BackgroundWorker クラスを使用して操作を別のスレッドで実行できます。
このチュートリアルでは、BackgroundWorker クラスを使用して、時間のかかる計算を "バックグラウンドで" 実行しながら、ユーザー インターフェイスの応答性を維持する方法について説明します。ここでは、フィボナッチの数列を非同期的に計算するアプリケーションを作成します。大きなフィボナッチ数の計算には、著しく時間がかかることがありますが、メイン UI スレッドは、この遅延によって中断されず、計算の間、フォームは応答性を維持します。
このチュートリアルでは、以下のタスクを行います。
Windows ベース アプリケーションの作成
フォームでの BackgroundWorker の作成
非同期イベント ハンドラの追加
進行状況の報告機能とキャンセルのサポートの追加
この例で使用するコードの完全な一覧については、「方法 : バックグラウンド操作を使用するフォームを実装する」を参照してください。
メモ : |
---|
使用している設定またはエディションによっては、表示されるダイアログ ボックスやメニュー コマンドがヘルプに記載されている内容と異なる場合があります。設定を変更するには、[ツール] メニューの [設定のインポートとエクスポート] をクリックします。詳細については、「Visual Studio の設定」を参照してください。 |
プロジェクトの作成
最初にプロジェクトを作成し、フォームを設定します。
バックグラウンド操作を使用するフォームを作成するには
BackgroundWorkerExample という名前の Windows ベース アプリケーション プロジェクトを作成します。詳細については、「方法 : Windows アプリケーション プロジェクトを作成する」を参照してください。
ソリューション エクスプローラで、[Form1] を右クリックし、ショートカット メニューの [名前の変更] をクリックします。ファイル名を FibonacciCalculator に変更します。コード要素 "Form1" へのすべての参照の名前を変更するかどうかを確認するダイアログ ボックスが表示されたら、[はい] をクリックします。
[ツールボックス] の NumericUpDown コントロールをフォームにドラッグします。Minimum プロパティを 1、Maximum プロパティを 91 に設定します。
2 つの Button コントロールをフォームに追加します。
一方の Button コントロールの名前を startAsyncButton に変更し、Text プロパティを Start Async に設定します。もう一方の Button コントロールの名前を cancelAsyncButton に変更し、Text プロパティを Cancel Async に設定します。Enabled プロパティを false に設定します。
両方の Button コントロールの Click イベントに対するイベント ハンドラを作成します。詳細については、「方法 : デザイナを使用してイベント ハンドラを作成する」を参照してください。
[ツールボックス] の Label コントロールをフォームにドラッグし、名前を resultLabel に変更します。
[ツールボックス] の ProgressBar コントロールをフォームにドラッグします。
フォームでの BackgroundWorker の作成
非同期操作用の BackgroundWorker は、Windowsフォーム デザイナ を使用して作成できます。
デザイナを使用して BackgroundWorker を作成するには
- [ツールボックス] の [コンポーネント] タブから、BackgroundWorker をフォームにドラッグします。
非同期イベント ハンドラの追加
以上で、BackgroundWorker コンポーネントの非同期イベント用のイベント ハンドラを追加する準備ができました。バックグラウンドで実行する、時間のかかるフィボナッチ数列の計算操作は、これらのうちいずれかのイベント ハンドラによって呼び出されます。
非同期イベント ハンドラを実装するには
[プロパティ] ウィンドウの [イベント] をクリックします。DoWork イベントと RunWorkerCompleted イベントをダブルクリックして、イベント ハンドラを作成します。イベンド ハンドラの使い方の詳細については、「方法 : デザイナを使用してイベント ハンドラを作成する」を参照してください。
フォームで ComputeFibonacci という名前の新しいメソッドを作成します。このメソッドが実際の操作を実行し、バックグラウンドで動作します。このコードは、フィボナッチ アルゴリズムの反復実装を示しています。これは、著しく非効率な操作で、数が大きくなるにつれて、完了するまでにかかる時間が急激に長くなります。アプリケーションで大きな遅延が発生する操作の例として、このコードを使用します。
' This is the method that does the actual work. For this ' example, it computes a Fibonacci number and ' reports progress as it does its work. Function ComputeFibonacci( _ ByVal n As Integer, _ ByVal worker As BackgroundWorker, _ ByVal e As DoWorkEventArgs) As Long ' The parameter n must be >= 0 and <= 91. ' Fib(n), with n > 91, overflows a long. If n < 0 OrElse n > 91 Then Throw New ArgumentException( _ "value must be >= 0 and <= 91", "n") End If Dim result As Long = 0 ' Abort the operation if the user has canceled. ' Note that a call to CancelAsync may have set ' CancellationPending to true just after the ' last invocation of this method exits, so this ' code will not have the opportunity to set the ' DoWorkEventArgs.Cancel flag to true. This means ' that RunWorkerCompletedEventArgs.Cancelled will ' not be set to true in your RunWorkerCompleted ' event handler. This is a race condition. If worker.CancellationPending Then e.Cancel = True Else If n < 2 Then result = 1 Else result = ComputeFibonacci(n - 1, worker, e) + _ ComputeFibonacci(n - 2, worker, e) End If ' Report progress as a percentage of the total task. Dim percentComplete As Integer = _ CSng(n) / CSng(numberToCompute) * 100 If percentComplete > highestPercentageReached Then highestPercentageReached = percentComplete worker.ReportProgress(percentComplete) End If End If Return result End Function
// This is the method that does the actual work. For this // example, it computes a Fibonacci number and // reports progress as it does its work. long ComputeFibonacci(int n, BackgroundWorker worker, DoWorkEventArgs e) { // The parameter n must be >= 0 and <= 91. // Fib(n), with n > 91, overflows a long. if ((n < 0) || (n > 91)) { throw new ArgumentException( "value must be >= 0 and <= 91", "n"); } long result = 0; // Abort the operation if the user has canceled. // Note that a call to CancelAsync may have set // CancellationPending to true just after the // last invocation of this method exits, so this // code will not have the opportunity to set the // DoWorkEventArgs.Cancel flag to true. This means // that RunWorkerCompletedEventArgs.Cancelled will // not be set to true in your RunWorkerCompleted // event handler. This is a race condition. if (worker.CancellationPending) { e.Cancel = true; } else { if (n < 2) { result = 1; } else { result = ComputeFibonacci(n - 1, worker, e) + ComputeFibonacci(n - 2, worker, e); } // Report progress as a percentage of the total task. int percentComplete = (int)((float)n / (float)numberToCompute * 100); if (percentComplete > highestPercentageReached) { highestPercentageReached = percentComplete; worker.ReportProgress(percentComplete); } } return result; }
// This is the method that does the actual work. For this // example, it computes a Fibonacci number and // reports progress as it does its work. long ComputeFibonacci( int n, BackgroundWorker^ worker, DoWorkEventArgs ^ e ) { // The parameter n must be >= 0 and <= 91. // Fib(n), with n > 91, overflows a long. if ( (n < 0) || (n > 91) ) { throw gcnew ArgumentException( "value must be >= 0 and <= 91","n" ); } long result = 0; // Abort the operation if the user has cancelled. // Note that a call to CancelAsync may have set // CancellationPending to true just after the // last invocation of this method exits, so this // code will not have the opportunity to set the // DoWorkEventArgs.Cancel flag to true. This means // that RunWorkerCompletedEventArgs.Cancelled will // not be set to true in your RunWorkerCompleted // event handler. This is a race condition. if ( worker->CancellationPending ) { e->Cancel = true; } else { if ( n < 2 ) { result = 1; } else { result = ComputeFibonacci( n - 1, worker, e ) + ComputeFibonacci( n - 2, worker, e ); } // Report progress as a percentage of the total task. int percentComplete = (int)((float)n / (float)numberToCompute * 100); if ( percentComplete > highestPercentageReached ) { highestPercentageReached = percentComplete; worker->ReportProgress( percentComplete ); } } return result; }
// This is the method that does the actual work. For this // example, it computes a Fibonacci number and // reports progress as it does its work. long ComputeFibonacci(int n, BackgroundWorker worker, DoWorkEventArgs e) { // The parameter n must be >= 0 and <= 91. // Fib(n), with n > 91, overflows a long. if (n < 0 || n > 91) { throw new ArgumentException("value must be >= 0 and <= 91", "n"); } long result = 0; // Abort the operation if the user has cancelled. // Note that a call to CancelAsync may have set // CancellationPending to true just after the // last invocation of this method exits, so this // code will not have the opportunity to set the // DoWorkEventArgs.Cancel flag to true. This means // that RunWorkerCompletedEventArgs.Cancelled will // not be set to true in your RunWorkerCompleted // event handler. This is a race condition. if (worker.get_CancellationPending()) { e.set_Cancel(true); } else { if (n < 2) { result = 1; } else { result = ComputeFibonacci(n - 1, worker, e) + ComputeFibonacci(n - 2, worker, e); } // Report progress as a percentage of the total task. int percentComplete=(int)((float)(n)/(float)(numberToCompute)* 100); if (percentComplete > highestPercentageReached) { highestPercentageReached = percentComplete; worker.ReportProgress(percentComplete); } } return result; }
DoWork イベント ハンドラで、ComputeFibonacci メソッドの呼び出しを追加します。ComputeFibonacci の最初のパラメータを、DoWorkEventArgs の Argument プロパティから受け取ります。BackgroundWorker パラメータと DoWorkEventArgs パラメータは、進行状況の報告機能とキャンセルのサポートのために後で使用します。
メモ : |
---|
DoWork イベント ハンドラは、backgroundWorker1 インスタンス変数を直接参照しないようにする必要があります。直接参照すると、このイベント ハンドラが BackgroundWorker の特定のインスタンスに結合されるからです。代わりに、該当するイベントを生成した BackgroundWorker への参照は、sender パラメータから復元します。このことは、フォームが複数の BackgroundWorker をホストする場合に重要です。 |
ComputeFibonacci の戻り値を DoWorkEventArgs の Result プロパティに代入します。この結果は、RunWorkerCompleted イベント ハンドラで利用できるようになります。
進行状況の報告機能とキャンセルのサポートの追加
時間のかかる非同期操作では、進行状況をユーザーに報告し、ユーザーが操作をキャンセルできるようにすることが望まれます。BackgroundWorker クラスには、バックグラウンド操作の進行状況を報告できるイベントが用意されています。また、ワーカー コードで CancelAsync の呼び出しを検出し、ワーカー コード自体を中断できるようにするフラグも使用できます。
進行状況の報告機能を実装するには
[プロパティ] ウィンドウで backgroundWorker1 を選択します。WorkerReportsProgress プロパティと WorkerSupportsCancellation プロパティを true に設定します。
FibonacciCalculator フォームで 2 つの変数を宣言します。これらの変数を使用して、進行状況を追跡します。
Private numberToCompute As Integer = 0 Private highestPercentageReached As Integer = 0
private int numberToCompute = 0; private int highestPercentageReached = 0;
int numberToCompute; int highestPercentageReached;
private int numberToCompute = 0; private int highestPercentageReached = 0;
ProgressChanged イベントのイベント ハンドラを追加します。ProgressChanged イベント ハンドラで、ProgressChangedEventArgs パラメータの ProgressPercentage プロパティを使用して、ProgressBar を更新します。
' This event handler updates the progress bar. Private Sub backgroundWorker1_ProgressChanged( _ ByVal sender As Object, ByVal e As ProgressChangedEventArgs) _ Handles backgroundWorker1.ProgressChanged Me.progressBar1.Value = e.ProgressPercentage End Sub
// This event handler updates the progress bar. private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.progressBar1.Value = e.ProgressPercentage; }
// This event handler updates the progress bar. void backgroundWorker1_ProgressChanged( Object^ /*sender*/, ProgressChangedEventArgs^ e ) { this->progressBar1->Value = e->ProgressPercentage; }
// This event handler updates the progress bar. private void backgroundWorker1_ProgressChanged(Object sender, ProgressChangedEventArgs e) { this.progressBar1.set_Value(e.get_ProgressPercentage()); } //backgroundWorker1_ProgressChanged
キャンセルのサポートを実装するには
cancelAsyncButton コントロールの Click イベント ハンドラで、非同期操作をキャンセルするコードを追加します。
Private Sub cancelAsyncButton_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles cancelAsyncButton.Click ' Cancel the asynchronous operation. Me.backgroundWorker1.CancelAsync() ' Disable the Cancel button. cancelAsyncButton.Enabled = False End Sub 'cancelAsyncButton_Click
private void cancelAsyncButton_Click(System.Object sender, System.EventArgs e) { // Cancel the asynchronous operation. this.backgroundWorker1.CancelAsync(); // Disable the Cancel button. cancelAsyncButton.Enabled = false; }
void cancelAsyncButton_Click( System::Object^ /*sender*/, System::EventArgs^ /*e*/ ) { // Cancel the asynchronous operation. this->backgroundWorker1->CancelAsync(); // Disable the Cancel button. cancelAsyncButton->Enabled = false; }
private void cancelAsyncButton_Click(Object sender, System.EventArgs e) { // Cancel the asynchronous operation. this.backgroundWorker1.CancelAsync(); // Disable the Cancel button. cancelAsyncButton.set_Enabled(false); }
進行状況を報告し、キャンセルをサポートする ComputeFibonacci メソッドのコード片を次に示します。
If worker.CancellationPending Then e.Cancel = True ... ' Report progress as a percentage of the total task. Dim percentComplete As Integer = _ CSng(n) / CSng(numberToCompute) * 100 If percentComplete > highestPercentageReached Then highestPercentageReached = percentComplete worker.ReportProgress(percentComplete) End If
if (worker.CancellationPending) { e.Cancel = true; } ... // Report progress as a percentage of the total task. int percentComplete = (int)((float)n / (float)numberToCompute * 100); if (percentComplete > highestPercentageReached) { highestPercentageReached = percentComplete; worker.ReportProgress(percentComplete); }
if ( worker->CancellationPending ) { e->Cancel = true; } ... // Report progress as a percentage of the total task. int percentComplete = (int)((float)n / (float)numberToCompute * 100); if ( percentComplete > highestPercentageReached ) { highestPercentageReached = percentComplete; worker->ReportProgress( percentComplete ); }
if (worker.get_CancellationPending()) { e.set_Cancel(true); } ... // Report progress as a percentage of the total task. int percentComplete=(int)((float)(n)/(float)(numberToCompute)* 100); if (percentComplete > highestPercentageReached) { highestPercentageReached = percentComplete; worker.ReportProgress(percentComplete); }
チェックポイント
この時点で、フィボナッチ電卓アプリケーションをコンパイルして実行できます。
プロジェクトをテストするには
F5 キーを押してアプリケーションをコンパイルおよび実行します。
計算がバックグラウンドで実行されている間、計算が完了するまでの進行状況が ProgressBar に表示されます。また、保留中の操作をキャンセルすることもできます。
小さい数の場合は計算に時間がかかりませんが、大きい数の場合は著しい遅延が生じます。30 以上の値を入力した場合、コンピュータの処理速度によっては、数秒間の遅延が生じます。値が 40 以上の場合は、計算が終了するまでに数分から数時間かかることがあります。電卓が大きなフィボナッチ数を計算している間でも、フォームを自由に移動したり、拡大縮小したり、閉じたりすることもできるます。これは、メイン UI スレッドが、計算の終了を待機していないからです。
次の手順
これで、バックグラウンドで計算を実行する BackgroundWorker コンポーネントを使用するフォームを実装できました。この後は、次のような非同期操作の他の可能性を検討してください。
複数の BackgroundWorker オブジェクトを使用して、複数の同時操作を実行する。
マルチスレッド アプリケーションをデバッグするには、「方法 : [スレッド] ウィンドウを使用する」を参照してください。
非同期プログラミング モデルをサポートする独自のコンポーネントを実装する。詳細については、「イベントベースの非同期パターンの概要」を参照してください。
注意 : マルチスレッドを使用している場合は、常にきわめて深刻で複雑なバグが発生する可能性があります。マルチスレッドを使用するソリューションを実装する前に、「マネージ スレッド処理の実施」を参照してください。