共用方式為


逐步解說:實作使用背景作業的表單

如果您有作業需要很長一段時間才能完成,而且您不想要使用者介面 (UI) 停止回應或封鎖,您可以使用 BackgroundWorker 類別,在其他執行緒上執行作業。

此逐步解說示範如何使用 BackgroundWorker 類別以在「背景」執行耗費時間的計算,同時使用者介面仍然保持回應。 當您進行逐步解說時,您必須讓應用程式以非同步方式計算 Fibonacci 數字。 即使計算大量 Fibonacci 數字需要很長一段時間,主要 UI 執行緒不會受到此延遲的中斷,表單在計算期間仍然有回應。

這個逐步解說中所述的工作包括:

  • 建立以 Windows 為基礎的應用程式

  • 在您的表單中建立 BackgroundWorker

  • 新增非同步事件處理常式

  • 新增進度報告和支援取消作業

如需此範例中使用之程式碼的完整清單,請參閱如何:實作使用背景作業的表單

建立使用背景作業的表單

  1. 在 Visual Studio 中,建立名為 BackgroundWorkerExample 的 Windows 應用程式專案 ([檔案]> [新增]> [專案]> [Visual C#] 或 [Visual Basic]> [傳統桌面]> [Windows Forms 應用程式])。

  2. 在 [方案總管] 中,以滑鼠右鍵按一下 [Form1],然後從捷徑功能表選取 [重新命名]。 將檔案名稱變更為 FibonacciCalculator。 當系統詢問您是否要重新命名程式碼元素 '' 的所有參考時,按一下 [是]Form1按鈕。

  3. NumericUpDown 控制項從 [工具箱] 拖曳至表單上。 將 Minimum 屬性設定為 1,並將 Maximum 屬性設定為 91

  4. 將兩個 Button 控制項新增至表單中。

  5. 將第一個 Button 控制項重新命名為 startAsyncButton,並將 Text 屬性設定為 Start Async。 將第二個 Button 控制項重新命名為 cancelAsyncButton,並將 Text 屬性設定為 Cancel Async。 將其 Enabled 屬性設定為 false

  6. 針對這兩個 Button 控制項的 Click 事件建立事件處理常式。 如需詳細資訊,請參閱如何:使用設計工具建立事件處理常式

  7. Label 控制項從 [工具箱] 拖曳至表單上,並將它重新命名為 resultLabel

  8. ProgressBar 控制項從 [工具箱] 拖曳至表單上。

使用設計工具建立 BackgroundWorker

您可以使用 BackgroundWorkerForms 設計工具建立用於非同步作業的

從 [工具箱] 的 [元件] 索引標籤,將 BackgroundWorker 拖曳至表單上。

新增非同步事件處理常式

您現在已準備要新增 BackgroundWorker 元件的非同步事件的事件處理常式。 將會在背景執行的耗費時間作業 (計算 Fibonacci 數字),會由其中一個事件處理常式呼叫。

  1. 在 [屬性] 視窗中,在 BackgroundWorker 元件保持選取的情況下,按一下 [事件] 按鈕。 按一下 DoWorkRunWorkerCompleted 事件以建立事件處理常式。 如需如何建立事件處理常式的詳細資訊,請參閱如何:使用設計工具建立事件處理常式

  2. 在表單中,建立名為 ComputeFibonacci 的新方法。 這個方法會執行實際的工作,並且會在背景執行。 此程式碼會示範 Fibonacci 演算法的遞迴實作,它非常沒有效率,對於較大的數字要耗費更長的時間才能完成。 它在這裡是針對說明目的使用,以示範會導致應用程式長時間延遲的作業。

    // 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 的第一個參數。 BackgroundWorkerDoWorkEventArgs 參數稍後將用於進度報告和取消支援。 將 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。 將 WorkerReportsProgressWorkerSupportsCancellation 屬性設定為 true

  2. 宣告 FibonacciCalculator 表單中的兩個變數。 這些項目會用來追蹤進度。

    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
    

檢查點

此時,您可以編譯並執行 Fibonacci 計算機應用程式。

F5 即可編譯和執行應用程式。

在背景執行計算時,您會看到 ProgressBar 顯示計算完成的進度。 您也可以取消暫止的作業。

對於小數字,計算應該非常快速,但是對於較大的數字,您應該會看到明顯的延遲。 如果您輸入的值為 30 或更大,您應該會看到幾秒鐘的延遲,取決於您的電腦速度。 對於大於 40 的值,可能需要數分鐘或數小時才能完成計算。 當計算機忙著計算大量 Fibonacci 數字時,請注意,您可以自由地到處移動表單、最小化、最大化,甚至是關閉表單。 這是因為主要 UI 執行緒並沒有在等待計算完成。

下一步

既然您已經實作表單,使用 BackgroundWorker 元件以在背景執行計算,您可以探索非同步作業的其他可能性:

另請參閱