次の方法で共有


チュートリアル : バックグラウンド操作を使用するフォームの実装

完了するまで時間がかかる操作があり、操作中にユーザー インターフェイス (UI) が応答を停止 (つまり "ハングアップ") しないようにするには、BackgroundWorker クラスを使用して操作を別のスレッドで実行できます。

このチュートリアルでは、BackgroundWorker クラスを使用して、時間のかかる計算を "バックグラウンドで" 実行しながら、ユーザー インターフェイスの応答性を維持する方法について説明します。 ここでは、フィボナッチの数列を非同期的に計算するアプリケーションを作成します。 大きなフィボナッチ数の計算には、著しく時間がかかることがありますが、メイン UI スレッドは、この遅延によって中断されず、計算の間、フォームは応答性を維持します。

このチュートリアルでは、以下のタスクを行います。

  • Windows ベース アプリケーションの作成

  • フォームでの BackgroundWorker の作成

  • 非同期イベント ハンドラーの追加

  • 進行状況の報告機能とキャンセルのサポートの追加

この例で使用するコードの完全な一覧については、「方法 : バックグラウンド操作を使用するフォームを実装する」を参照してください。

注意

実際に画面に表示されるダイアログ ボックスとメニュー コマンドは、アクティブな設定またはエディションによっては、ヘルプの説明と異なる場合があります。 設定を変更するには、[ツール] メニューの [設定のインポートとエクスポート] をクリックします。 詳細については、「設定の操作」を参照してください。

プロジェクトの作成

最初にプロジェクトを作成し、フォームを設定します。

バックグラウンド操作を使用するフォームを作成するには

  1. BackgroundWorkerExample という名前の Windows ベース アプリケーション プロジェクトを作成します。 詳細については、「方法: 新しい Windows フォーム アプリケーション プロジェクトを作成する」を参照してください。

  2. ソリューション エクスプローラーで、[Form1] を右クリックし、ショートカット メニューの [名前の変更] をクリックします。 ファイル名を FibonacciCalculator に変更します。 コード要素 "Form1" へのすべての参照の名前を変更するかどうかを確認するダイアログ ボックスが表示されたら、[はい] をクリックします。

  3. [ツールボックス]NumericUpDown コントロールをフォームにドラッグします。 Minimum プロパティを 1、Maximum プロパティを 91 に設定します。

  4. 2 つの Button コントロールをフォームに追加します。

  5. 一方の Button コントロールの名前を startAsyncButton に変更し、Text プロパティを Start Async に設定します。 もう一方の Button コントロールの名前を cancelAsyncButton に変更し、Text プロパティを Cancel Async に設定します。 Enabled プロパティを false に設定します。

  6. 両方の Button コントロールの Click イベントに対するイベント ハンドラーを作成します。 詳細については、「方法 : デザイナーを使用してイベント ハンドラーを作成する」を参照してください。

  7. [ツールボックス]Label コントロールをフォームにドラッグし、名前を resultLabel に変更します。

  8. [ツールボックス]ProgressBar コントロールをフォームにドラッグします。

フォームでの BackgroundWorker の作成

非同期操作用の BackgroundWorker は、Windows フォーム デザイナー を使用して作成できます。

デザイナーを使用して BackgroundWorker を作成するには

  • [ツールボックス][コンポーネント] タブから、BackgroundWorker をフォームにドラッグします。

非同期イベント ハンドラーの追加

以上で、BackgroundWorker コンポーネントの非同期イベント用のイベント ハンドラーを追加する準備ができました。 バックグラウンドで実行する、時間のかかるフィボナッチ数列の計算操作は、これらのうちいずれかのイベント ハンドラーによって呼び出されます。

非同期イベント ハンドラーを実装するには

  1. [プロパティ] ウィンドウで BackgroundWorker コンポーネントが選択されている状態で、[イベント] ボタンをクリックします。 DoWork イベントと RunWorkerCompleted イベントをダブルクリックして、イベント ハンドラーを作成します。 イベント ハンドラーの使用方法の詳細については、「方法 : デザイナーを使用してイベント ハンドラーを作成する」を参照してください。

  2. フォームで 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;
    }
    
  3. DoWork イベント ハンドラーで、ComputeFibonacci メソッドの呼び出しを追加します。 ComputeFibonacci の最初のパラメーターを、DoWorkEventArgsArgument プロパティから受け取ります。 BackgroundWorker パラメーターと DoWorkEventArgs パラメーターは、進行状況の報告機能とキャンセルのサポートのために後で使用します。 ComputeFibonacci の戻り値を DoWorkEventArgsResult プロパティに代入します。 この結果は、RunWorkerCompleted イベント ハンドラーで利用できるようになります。

    注意

    DoWork イベント ハンドラーは、backgroundWorker1 インスタンス変数を直接参照しません。直接参照すると、このイベント ハンドラーが BackgroundWorker の特定のインスタンスに結合されるためです。 代わりに、該当するイベントを生成した BackgroundWorker への参照は、sender パラメーターから復元します。 このことは、フォームが複数の BackgroundWorker をホストする場合に重要です。 また、DoWork イベント ハンドラーでユーザー インターフェイス オブジェクトを操作しないようにしてください。 代わりに、BackgroundWorker のイベントを通じてユーザー インターフェイスと通信します。

    ' This event handler is where the actual work is done.
    Private Sub backgroundWorker1_DoWork( _
    ByVal sender As Object, _
    ByVal e As DoWorkEventArgs) _
    Handles backgroundWorker1.DoWork
    
        ' Get the BackgroundWorker object that raised this event.
        Dim worker As BackgroundWorker = _
            CType(sender, BackgroundWorker)
    
        ' Assign the result of the computation
        ' to the Result property of the DoWorkEventArgs
        ' object. This is will be available to the 
        ' RunWorkerCompleted eventhandler.
        e.Result = ComputeFibonacci(e.Argument, worker, e)
    End Sub 'backgroundWorker1_DoWork
    
    // This event handler is where the actual,
    // potentially time-consuming work is done.
    private void backgroundWorker1_DoWork(object sender, 
        DoWorkEventArgs e)
    {   
        // Get the BackgroundWorker that raised this event.
        BackgroundWorker worker = sender as BackgroundWorker;
    
        // Assign the result of the computation
        // to the Result property of the DoWorkEventArgs
        // object. This is will be available to the 
        // RunWorkerCompleted eventhandler.
        e.Result = ComputeFibonacci((int)e.Argument, worker, e);
    }
    
    // This event handler is where the actual,
    // potentially time-consuming work is done.
    void backgroundWorker1_DoWork( Object^ sender, DoWorkEventArgs^ e )
    {
       // Get the BackgroundWorker that raised this event.
       BackgroundWorker^ worker = dynamic_cast<BackgroundWorker^>(sender);
    
       // Assign the result of the computation
       // to the Result property of the DoWorkEventArgs
       // object. This is will be available to the 
       // RunWorkerCompleted eventhandler.
       e->Result = ComputeFibonacci( safe_cast<Int32>(e->Argument), worker, e );
    }
    
  4. startAsyncButton コントロールの Click イベント ハンドラーで、非同期操作を開始するコードを追加します。

    Private Sub startAsyncButton_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles startAsyncButton.Click
    
        ' Reset the text in the result label.
        resultLabel.Text = [String].Empty
    
        ' Disable the UpDown control until 
        ' the asynchronous operation is done.
        Me.numericUpDown1.Enabled = False
    
        ' Disable the Start button until 
        ' the asynchronous operation is done.
        Me.startAsyncButton.Enabled = False
    
        ' Enable the Cancel button while 
        ' the asynchronous operation runs.
        Me.cancelAsyncButton.Enabled = True
    
        ' Get the value from the UpDown control.
        numberToCompute = CInt(numericUpDown1.Value)
    
        ' Reset the variable for percentage tracking.
        highestPercentageReached = 0
    
    
        ' Start the asynchronous operation.
        backgroundWorker1.RunWorkerAsync(numberToCompute)
    End Sub 
    
    private void startAsyncButton_Click(System.Object sender, 
        System.EventArgs e)
    {
        // Reset the text in the result label.
        resultLabel.Text = String.Empty;
    
        // Disable the UpDown control until 
        // the asynchronous operation is done.
        this.numericUpDown1.Enabled = false;
    
        // Disable the Start button until 
        // the asynchronous operation is done.
        this.startAsyncButton.Enabled = false;
    
        // Enable the Cancel button while 
        // the asynchronous operation runs.
        this.cancelAsyncButton.Enabled = true;
    
        // Get the value from the UpDown control.
        numberToCompute = (int)numericUpDown1.Value;
    
        // Reset the variable for percentage tracking.
        highestPercentageReached = 0;
    
        // Start the asynchronous operation.
        backgroundWorker1.RunWorkerAsync(numberToCompute);
    }
    
    void startAsyncButton_Click( System::Object^ /*sender*/, System::EventArgs^ /*e*/ )
    {
    
       // Reset the text in the result label.
       resultLabel->Text = String::Empty;
    
       // Disable the UpDown control until 
       // the asynchronous operation is done.
       this->numericUpDown1->Enabled = false;
    
       // Disable the Start button until 
       // the asynchronous operation is done.
       this->startAsyncButton->Enabled = false;
    
       // Enable the Cancel button while 
       // the asynchronous operation runs.
       this->cancelAsyncButton->Enabled = true;
    
       // Get the value from the UpDown control.
       numberToCompute = (int)numericUpDown1->Value;
    
       // Reset the variable for percentage tracking.
       highestPercentageReached = 0;
    
       // Start the asynchronous operation.
       backgroundWorker1->RunWorkerAsync( numberToCompute );
    }
    
  5. RunWorkerCompleted イベント ハンドラーで、resultLabel コントロールに計算結果を代入します。

    ' This event handler deals with the results of the
    ' background operation.
    Private Sub backgroundWorker1_RunWorkerCompleted( _
    ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) _
    Handles backgroundWorker1.RunWorkerCompleted
    
        ' First, handle the case where an exception was thrown.
        If (e.Error IsNot Nothing) Then
            MessageBox.Show(e.Error.Message)
        ElseIf e.Cancelled Then
            ' Next, handle the case where the user canceled the 
            ' operation.
            ' Note that due to a race condition in 
            ' the DoWork event handler, the Cancelled
            ' flag may not have been set, even though
            ' CancelAsync was called.
            resultLabel.Text = "Canceled"
        Else
            ' Finally, handle the case where the operation succeeded.
            resultLabel.Text = e.Result.ToString()
        End If
    
        ' Enable the UpDown control.
        Me.numericUpDown1.Enabled = True
    
        ' Enable the Start button.
        startAsyncButton.Enabled = True
    
        ' Disable the Cancel button.
        cancelAsyncButton.Enabled = False
    End Sub 'backgroundWorker1_RunWorkerCompleted
    
    // This event handler deals with the results of the
    // background operation.
    private void backgroundWorker1_RunWorkerCompleted(
        object sender, RunWorkerCompletedEventArgs e)
    {
        // First, handle the case where an exception was thrown.
        if (e.Error != null)
        {
            MessageBox.Show(e.Error.Message);
        }
        else if (e.Cancelled)
        {
            // Next, handle the case where the user canceled 
            // the operation.
            // Note that due to a race condition in 
            // the DoWork event handler, the Cancelled
            // flag may not have been set, even though
            // CancelAsync was called.
            resultLabel.Text = "Canceled";
        }
        else
        {
            // Finally, handle the case where the operation 
            // succeeded.
            resultLabel.Text = e.Result.ToString();
        }
    
        // Enable the UpDown control.
        this.numericUpDown1.Enabled = true;
    
        // Enable the Start button.
        startAsyncButton.Enabled = true;
    
        // Disable the Cancel button.
        cancelAsyncButton.Enabled = false;
    }
    
    // This event handler deals with the results of the
    // background operation.
    void backgroundWorker1_RunWorkerCompleted( Object^ /*sender*/, RunWorkerCompletedEventArgs^ e )
    {
       // First, handle the case where an exception was thrown.
       if ( e->Error != nullptr )
       {
          MessageBox::Show( e->Error->Message );
       }
       else
       if ( e->Cancelled )
       {
          // Next, handle the case where the user cancelled 
          // the operation.
          // Note that due to a race condition in 
          // the DoWork event handler, the Cancelled
          // flag may not have been set, even though
          // CancelAsync was called.
          resultLabel->Text = "Cancelled";
       }
       else
       {
          // Finally, handle the case where the operation 
          // succeeded.
          resultLabel->Text = e->Result->ToString();
       }
    
       // Enable the UpDown control.
       this->numericUpDown1->Enabled = true;
    
       // Enable the Start button.
       startAsyncButton->Enabled = true;
    
       // Disable the Cancel button.
       cancelAsyncButton->Enabled = false;
    }
    

進行状況の報告機能とキャンセルのサポートの追加

時間のかかる非同期操作では、進行状況をユーザーに報告し、ユーザーが操作をキャンセルできるようにすることが望まれます。 BackgroundWorker クラスには、バックグラウンド操作の進行状況を報告できるイベントが用意されています。 また、ワーカー コードで CancelAsync の呼び出しを検出し、ワーカー コード自体を中断できるようにするフラグも使用できます。

進行状況の報告機能を実装するには

  1. [プロパティ] ウィンドウで backgroundWorker1 を選択します。 WorkerReportsProgress プロパティと WorkerSupportsCancellation プロパティを true に設定します。

  2. FibonacciCalculator フォームで 2 つの変数を宣言します。 これらの変数を使用して、進行状況を追跡します。

    Private numberToCompute As Integer = 0
    Private highestPercentageReached As Integer = 0
    
    private int numberToCompute = 0;
    private int highestPercentageReached = 0;
    
    int numberToCompute;
    int highestPercentageReached;
    
  3. 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;
    }
    

キャンセルのサポートを実装するには

  1. 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;
    }
    
  2. 進行状況を報告し、キャンセルをサポートする 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 );
    }
    

チェックポイント

この時点で、フィボナッチ電卓アプリケーションをコンパイルして実行できます。

プロジェクトをテストするには

  • F5 キーを押してアプリケーションをコンパイルおよび実行します。

    計算がバックグラウンドで実行されている間、計算が完了するまでの進行状況が ProgressBar に表示されます。 また、保留中の操作をキャンセルすることもできます。

    小さい数の場合は計算に時間がかかりませんが、大きい数の場合は著しい遅延が生じます。 30 以上の値を入力した場合、コンピューターの処理速度によっては、数秒間の遅延が生じます。 値が 40 以上の場合は、計算が終了するまでに数分から数時間かかることがあります。 電卓が大きなフィボナッチ数を計算している間でも、フォームを自由に移動したり、拡大縮小したり、閉じたりすることもできるます。 これは、メイン UI スレッドが、計算の終了を待機していないからです。

次の手順

これで、バックグラウンドで計算を実行する BackgroundWorker コンポーネントを使用するフォームを実装できました。この後は、次のような非同期操作の他の可能性を検討してください。

参照

処理手順

方法 : バックグラウンド操作を使用するフォームを実装する

チュートリアル : 操作をバックグラウンドで実行する

参照

BackgroundWorker

概念

マネージ スレッド処理の実施

その他の技術情報

コンポーネントのマルチスレッド

Visual Basic におけるマルチスレッド

BackgroundWorker コンポーネント