次の方法で共有


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

完了に時間がかかる操作があり、ユーザー インターフェイス (UI) が応答を停止したりブロックしたりしたくない場合は、 BackgroundWorker クラスを使用して別のスレッドで操作を実行できます。

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

このチュートリアルで説明するタスクは次のとおりです。

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

  • フォームで BackgroundWorker を作成する

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

  • 進行状況レポートの追加とキャンセルのサポート

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

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

  1. Visual Studio で、BackgroundWorkerExample (File>New>Project>Visual C#または visual Basic>Classic Desktop>Windows フォーム アプリケーション) という Windows ベースのアプリケーション プロジェクトを作成します。

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

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

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

  5. 最初の Button コントロール startAsyncButton の名前を変更し、 Text プロパティを Start Async に設定します。 2 番目の Button コントロール cancelAsyncButtonの名前を変更し、 Text プロパティを Cancel Async に設定します。 その Enabled プロパティを falseに設定します。

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

  7. Labelからフォームに コントロールをドラッグし、resultLabel名前を変更します。

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

デザイナーで BackgroundWorker を作成する

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

ツールボックスの [コンポーネント] タブから、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.
    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 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.
    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
    
  3. DoWork イベント ハンドラーで、ComputeFibonacci メソッドの呼び出しを追加します。 ComputeFibonacciArgument プロパティから、DoWorkEventArgsの最初のパラメーターを取得します。 BackgroundWorkerパラメーターとDoWorkEventArgs パラメーターは、後で進行状況の報告と取り消しのサポートに使用されます。 ComputeFibonacciの戻り値をResultDoWorkEventArgs プロパティに割り当てます。 この結果は、 RunWorkerCompleted イベント ハンドラーで使用できます。

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

    // 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 );
    }
    
    // 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 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
    
  4. startAsyncButton コントロールのClick イベント ハンドラーに、非同期操作を開始するコードを追加します。

    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 );
    }
    
    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);
    }
    
    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 
    
  5. RunWorkerCompleted イベント ハンドラーで、計算結果をresultLabel コントロールに割り当てます。

    // 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;
    }
    
    // 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.
    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
    

進行状況レポートの追加とキャンセルのサポート

長い時間がかかる非同期操作の場合、多くの場合、進行状況をユーザーに報告し、ユーザーが操作を取り消せるようにすることが望ましい場合があります。 BackgroundWorker クラスには、バックグラウンド操作の進行に合わせて進行状況を投稿できるイベントが用意されています。 また、ワーカー コードが CancelAsync への呼び出しを検出し、それ自体を中断できるようにするフラグも用意されています。

進行状況レポートを実装する

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

  2. FibonacciCalculator 形式で 2 つの変数を宣言します。 これらは進行状況を追跡するために使用されます。

    int numberToCompute;
    int highestPercentageReached;
    
    private int numberToCompute = 0;
    private int highestPercentageReached = 0;
    
    Private numberToCompute As Integer = 0
    Private highestPercentageReached As Integer = 0
    
  3. ProgressChanged イベントのイベント ハンドラーを追加します。 ProgressChanged イベント ハンドラーで、ProgressBar パラメーターの ProgressPercentage プロパティを使用してProgressChangedEventArgsを更新します。

    // 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.Value = e.ProgressPercentage;
    }
    
    ' 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
    

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

  1. cancelAsyncButton コントロールのClick イベント ハンドラーに、非同期操作をキャンセルするコードを追加します。

    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(System.Object sender,
        System.EventArgs e)
    {
        // Cancel the asynchronous operation.
        this.backgroundWorker1.CancelAsync();
    
        // Disable the Cancel button.
        cancelAsyncButton.Enabled = false;
    }
    
    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
    
  2. ComputeFibonacci メソッドの次のコード フラグメントは、進行状況を報告し、取り消しをサポートします。

    if ( worker->CancellationPending )
    {
       e->Cancel = true;
    }
    
    if (worker.CancellationPending)
    {
        e.Cancel = true;
    }
    
    If worker.CancellationPending Then
        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 );
    }
    
    // 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);
    }
    
    ' 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
    

チェックポイント

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

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

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

小さい数値の場合、計算は非常に高速ですが、数値が大きい場合は、顕著な遅延が発生します。 30 以上の値を入力すると、コンピューターの速度に応じて数秒の遅延が表示されます。 40 より大きい値の場合、計算が完了するまでに数分または数時間かかることがあります。 電卓が大きなフィボナッチ数の計算に忙しい間は、フォームを自由に動かしたり、最小化したり、最大化したり、閉じることもできます。 これは、メイン UI スレッドが計算の完了を待機していないためです。

次のステップ

BackgroundWorker コンポーネントを使用してバックグラウンドで計算を実行するフォームを実装したので、非同期操作の他の可能性を調べることができます。

こちらも参照ください