逐步解說:實作使用背景作業的表單
如果您有必須花費長時間才能完成的作業,且不希望使用者介面 (UI) 停止回應或「無回應」(hang),您可以使用 BackgroundWorker 類別在另一個執行緒上執行作業。
這個逐步解說會說明如何使用 BackgroundWorker 類別「於幕後」執行耗時的計算,讓使用者介面保持回應能力。 當您完成後,會擁有非同步計算費氏數的應用程式。 即使計算大的費氏數會耗費可觀的時間,主要的 UI 執行緒也不會受此延遲中斷,而表單在計算期間將具有回應能力。
逐步解說將說明的工作包括:
建立 Windows 架構應用程式
在表單上建立 BackgroundWorker
加入非同步事件處理常式
加入進度報表和取消的支援
如需在此範例中使用的完整程式碼清單,請參閱 HOW TO:實作使用背景作業的表單。
注意事項 |
---|
根據您目前使用的設定或版本,您所看到的對話方塊與功能表指令可能會與 [說明] 中描述的不同。 若要變更設定,請從 [工具] 功能表中選取 [匯入和匯出設定]。 如需詳細資訊,請參閱 使用設定。 |
建立專案
第一個步驟是建立專案並設定表單。
若要建立使用背景作業的表單
建立名為 BackgroundWorkerExample 的 Windows 架構應用程式專案。 如需詳細資訊,請參閱HOW TO:建立新的 Windows Form 應用程式專案。
在 [方案總管] 中以滑鼠右鍵按一下 [Form1],並從捷徑功能表中選取 [重新命名]。 將檔案名稱變更為 FibonacciCalculator。 當詢問您是否要重新命名對程式碼項目「Form1」的所有參考時,請按一下 [是] 按鈕。
將 NumericUpDown 控制項從 [工具箱] 拖曳到表單上。 將 Minimum 屬性設定為 1,並將 Maximum 屬性設定為 91。
加入兩個 Button 控制項至表單。
重新命名第一個 Button 控制項 startAsyncButton,並將 Text 屬性設定為 Start Async。 重新命名第二個 Button 控制項 cancelAsyncButton,並將 Text 屬性設定為 Cancel Async。 將 Enabled 屬性設定為 false。
為 Button 控制項的兩個 Click 事件建立事件處理常式。 如需詳細資訊,請參閱HOW TO:使用設計工具建立事件處理常式。
將 Label 控制項從 [工具箱] 拖曳到表單上,並將其重新命名為 resultLabel。
將 ProgressBar 控制項從 [工具箱] 拖曳到表單上。
在表單中建立 BackgroundWorker
您可以使用 [Windows Forms 設計工具] 建立非同步作業的 BackgroundWorker。
若要使用設計工具建立 BackgroundWorker
- 從 [工具箱] 的 [元件] 索引標籤中,將 BackgroundWorker 拖曳至表單上。
加入非同步事件處理常式
您現在已準備好要為 BackgroundWorker 元件的非同步事件加入事件處理常式。 將於幕後執行的耗時作業 (用來計算費氏數),會由這些事件處理常式的其中一個進行呼叫。
若要實作非同步事件處理常式
在 [屬性] 視窗中,在仍選取 BackgroundWorker 元件的情況下,按一下 [事件] 按鈕。 按兩下 DoWork 和 RunWorkerCompleted 事件以建立事件處理常式。 如需如何使用事件處理常式的詳細資訊,請參閱HOW TO:使用設計工具建立事件處理常式。
在表單中建立名為 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; }
在 DoWork 事件處理常式中加入對 ComputeFibonacci 方法的呼叫。 使用 DoWorkEventArgs 的 Argument 屬性做為 ComputeFibonacci 的第一個參數。 BackgroundWorker 和 DoWorkEventArgs 參數稍後將會用來做為進度報表和取消支援。 將 ComputeFibonacci 的傳回值指派給 DoWorkEventArgs 的 Result 屬性。 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 ); }
在 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 ); }
在 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 的呼叫並加以中斷。
若要實作進度報表
在 [屬性] 視窗中選取 backgroundWorker1。 將 WorkerReportsProgress 和 WorkerSupportsCancellation 屬性設定為 true。
在 FibonacciCalculator 表單中宣告兩個變數, 這些將用來追蹤進度。
Private numberToCompute As Integer = 0 Private highestPercentageReached As Integer = 0
private int numberToCompute = 0; private int highestPercentageReached = 0;
int numberToCompute; int highestPercentageReached;
加入 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; }
若要實作取消的支援
在 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; }
下列 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 物件。
若要偵錯您的多執行緒應用程式,請參閱 HOW TO:使用執行緒視窗。
實作支援非同步程式撰寫模型 (Programming Model) 的元件。 如需詳細資訊,請參閱 事件架構非同步模式概觀。
警告
在使用任何類型的多執行緒時,很可能會接觸到極嚴重且複雜的錯誤。 在實作任何使用多執行緒處理的方案之前,請參閱 Managed 執行緒處理的最佳實施方針。