共用方式為


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

如果您有必須花費長時間才能完成的作業,且不希望使用者介面 (UI) 停止回應或「無回應」(hang),您可以使用 BackgroundWorker 類別在另一個執行緒上執行作業。

這個逐步解說會說明如何使用 BackgroundWorker 類別「於幕後」執行耗時的計算,讓使用者介面保持回應能力。 當您完成後,會擁有非同步計算費氏數的應用程式。 即使計算大的費氏數會耗費可觀的時間,主要的 UI 執行緒也不會受此延遲中斷,而表單在計算期間將具有回應能力。

逐步解說將說明的工作包括:

  • 建立 Windows 架構應用程式

  • 在表單上建立 BackgroundWorker

  • 加入非同步事件處理常式

  • 加入進度報表和取消的支援

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

注意事項注意事項

根據您目前使用的設定或版本,您所看到的對話方塊與功能表指令可能會與 [說明] 中描述的不同。 若要變更設定,請從 [工具] 功能表中選取 [匯入和匯出設定]。 如需詳細資訊,請參閱 使用設定

建立專案

第一個步驟是建立專案並設定表單。

若要建立使用背景作業的表單

  1. 建立名為 BackgroundWorkerExample 的 Windows 架構應用程式專案。 如需詳細資訊,請參閱HOW TO:建立新的 Windows Form 應用程式專案

  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 事件建立事件處理常式。 如需詳細資訊,請參閱HOW TO:使用設計工具建立事件處理常式

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

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

在表單中建立 BackgroundWorker

您可以使用 [Windows Forms 設計工具] 建立非同步作業的 BackgroundWorker

若要使用設計工具建立 BackgroundWorker

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

加入非同步事件處理常式

您現在已準備好要為 BackgroundWorker 元件的非同步事件加入事件處理常式。 將於幕後執行的耗時作業 (用來計算費氏數),會由這些事件處理常式的其中一個進行呼叫。

若要實作非同步事件處理常式

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

  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 方法的呼叫。 使用 DoWorkEventArgsArgument 屬性做為 ComputeFibonacci 的第一個參數。 BackgroundWorkerDoWorkEventArgs 參數稍後將會用來做為進度報表和取消支援。 將 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。 將 WorkerReportsProgressWorkerSupportsCancellation 屬性設定為 true。

  2. 在 FibonacciCalculator 表單中宣告兩個變數, 這些將用來追蹤進度。

    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 元件在幕後執行計算的表單,現在可以探索非同步作業的其他可能性:

請參閱

工作

HOW TO:實作使用背景作業的表單

逐步解說:在背景執行作業

參考

BackgroundWorker

概念

Managed 執行緒處理的最佳實施方針

其他資源

元件中的多執行緒

Multithreading in Visual Basic

BackgroundWorker 元件