Exemplarische Vorgehensweise: Implementieren eines Formulars, das eine Hintergrundoperation verwendet
Wenn Sie eine zeitintensive Operation ausführen müssen und Sie vermeiden möchten, dass Ihre Benutzeroberfläche (UI) nicht mehr reagiert oder gar abstürzt, können Sie den Vorgang mithilfe der Klasse BackgroundWorker in einem anderen Thread ausführen.
Diese exemplarische Vorgehensweise veranschaulicht, wie Sie die BackgroundWorker-Klasse dazu nutzen können, zeitintensive Berechnungen „im Hintergrund“ auszuführen, während die Benutzeroberfläche reaktionsfähig bleibt. Wenn Sie diese exemplarische Vorgehensweise abgeschlossen haben, verfügen Sie über eine Anwendung, die Fibonacci-Zahlen asynchron berechnet. Obwohl das Berechnen einer großen Fibonacci-Zahl merklich Zeit in Anspruch nimmt, wird der Haupt-UI-Thread nicht von dieser Verzögerung unterbrochen, und das Formular behält während der Berechnung seine Reaktionsfähigkeit.
In dieser exemplarischen Vorgehensweise werden u. a. folgende Aufgaben veranschaulicht:
Erstellen einer Windows-basierten Anwendung
Erstellen eines BackgroundWorker in Ihrem Formular
Hinzufügen Asynchroner Ereignishandler
Hinzufügen von Statusberichterstellung und Abbruchunterstützung
Eine vollständige Liste des in diesem Beispiel verwendeten Codes finden Sie unter Vorgehensweise: Implementieren eines Formulars, das eine Hintergrundoperation verwendet.
So erstellen Sie ein Formular mit Verwendung eines Hintergrundvorgangs
Erstellen Sie in Visual Studio ein Windows-basiertes Anwendungsprojekt mit dem Namen
BackgroundWorkerExample
(Datei>Neu>Projekt>Visual C# oder Visual Basic>Klassischer Desktop>Windows Forms-Anwendung).Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf Form1, und wählen Sie im Kontextmenü Umbenennen aus. Ändern Sie den Dateinamen in
FibonacciCalculator
. Klicken Sie auf die Schaltfläche Ja, wenn Sie gefragt werden, ob alle Verweise auf das CodeelementForm1
umbenannt werden sollen.Ziehen Sie ein NumericUpDown-Steuerelement von der Toolbox auf das Formular. Legen Sie die Minimum-Eigenschaft auf
1
und die Maximum-Eigenschaft auf91
fest.Fügen Sie dem Formular zwei Button-Steuerelemente hinzu.
Geben Sie dem ersten Button-Steuerelement den Namen
startAsyncButton
, und legen Sie die Eigenschaft Text aufStart Async
fest. Geben Sie dem zweiten Button-Steuerelement den NamencancelAsyncButton
, und legen Sie die Eigenschaft Text aufCancel Async
fest. Legen Sie die zugehörige Enabled-Eigenschaft auffalse
fest.Erstellen Sie einen Ereignishandler für beide Button-Ereignisse des Click- Steuerelements. Weitere Informationen finden Sie unter Vorgehensweise: Erstellen von Ereignishandlern mithilfe des Designers.
Ziehen Sie ein Label-Steuerelement aus der Toolbox auf das Formular, und benennen Sie es in
resultLabel
um.Ziehen Sie ein ProgressBar-Steuerelement von der Toolbox auf das Formular.
Erstellen eines BackgroundWorker mit dem Designer
Sie können den BackgroundWorker für Ihren asynchronen Vorgang mit dem WindowsForms-Designer erstellen.
Zielen Sie einen von der Registerkarte Komponenten der ToolboxBackgroundWorker auf das Formular.
Hinzufügen asynchroner Ereignishandler
Jetzt können Sie Ereignishandler für die asynchronen Ereignisse der Komponente BackgroundWorker hinzufügen. Der zeitaufwändige Vorgang im Hintergrund, der die Fibonacci-Zahlen berechnet, wird von einem dieser Ereignishandler aufgerufen.
Klicken Sie im Fenster Eigenschaften auf die Schaltfläche BackgroundWorker, während die Komponente weiterhin ausgewählt ist. Doppelklicken Sie auf die Ereignisse DoWork und RunWorkerCompleted, um Ereignishandler zu erstellen. Informationen zur Verwendung von Ereignishandlern finden Sie unter Vorgehensweise: Erstellen von Ereignishandlern mithilfe des Designers.
Erstellen Sie im Formular eine neue Methode namens
ComputeFibonacci
. Diese Methode führt die eigentliche Arbeit aus und wird im Hintergrund ausgeführt. Dieser Code veranschaulicht die rekursive Umsetzung des Fibonacci-Algorithmus, der deutlich ineffizient ist und wesentlich mehr Zeit in Anspruch nimmt, große Zahlen abzuschließen. Er wird hier verwendet, um einen Vorgang zu veranschaulichen, der lange Verzögerungen in der Anwendung verursachen kann.// 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
Fügen Sie im Ereignishandler DoWork einen Aufruf der Methode
ComputeFibonacci
hinzu. Verwenden Sie den ersten Parameter fürComputeFibonacci
aus der Argument-Eigenschaft von DoWorkEventArgs. Die Parameter BackgroundWorker und DoWorkEventArgs werden später für die Statusberichterstellung und Abbruchunterstützung verwendet. Weisen Sie den Rückgabewert vonComputeFibonacci
der Result-Eigenschaft von DoWorkEventArgs zu. Dieses Ergebnis wird dem RunWorkerCompleted-Ereignishandler zur Verfügung gestellt.Hinweis
Der Ereignishandler DoWork verweist nicht direkt auf die Instanzvariable
backgroundWorker1
, da dies den Ereignishandler mit einer spezifischen Instanz von BackgroundWorker koppeln würde. Stattdessen wird ein Verweis auf BackgroundWorker, der dieses Ereignis ausgelöst hat, aus demsender
-Parameter wiederhergestellt. Dies ist wichtig, wenn das Formular mehr als einen BackgroundWorker hostet. Es ist auch wichtig, keine Benutzerschnittstellenobjekte in Ihrem DoWork-Ereignishandler zu ändern. Kommunizieren Sie stattdessen über BackgroundWorker-Ereignisse mit der Benutzeroberfläche.// 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
Fügen Sie dem
startAsyncButton
-Ereignishandler des Click-Steuerelements den Code hinzu, der den asynchronen Vorgang startet.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
Weisen Sie im RunWorkerCompleted-Ereignishandler das Ergebnis der Berechnung dem
resultLabel
-Steuerelement zu.// 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
Hinzufügen von Statusberichterstellung und Abbruchunterstützung
Bei asynchronen Vorgängen, die viel Zeit in Anspruch nehmen, ist es oft ratsam, dem Benutzer den Fortschritt zu berichten und die Möglichkeit zu geben, den Vorgang abzubrechen. Die Klasse BackgroundWorker stellt ein Ereignis bereit, mit dem Sie den Status übermitteln können, während der Hintergrundvorgang ausgeführt wird. Sie stellt außerdem ein Flag bereit, mit dem Ihr Workercode einen Aufruf von CancelAsync identifizieren und sich selbst unterbrechen kann.
So implementieren Sie die Statusberichterstellung
Wählen Sie im Fenster Eigenschaften
backgroundWorker1
aus. Legen Sie für die Eigenschaften WorkerReportsProgress (Breite) und WorkerSupportsCancellation (Höhe) den Werttrue
fest.Deklarieren Sie zwei Variablen im Formular
FibonacciCalculator
. Diese werden verwendet, um den Fortschritt nachzuverfolgen.int numberToCompute; int highestPercentageReached;
private int numberToCompute = 0; private int highestPercentageReached = 0;
Private numberToCompute As Integer = 0 Private highestPercentageReached As Integer = 0
Fügen Sie einen Ereignishandler für das ProgressChanged-Ereignis hinzu. Aktualisieren Sie im ProgressChanged-Ereignishandler ProgressBar mit der ProgressPercentage-Eigenschaft des ProgressChangedEventArgs-Parameters.
// 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
Implementieren der Abbruchunterstützung
Fügen Sie dem
cancelAsyncButton
-Ereignishandler des Click-Steuerelements den Code hinzu, der den asynchronen Vorgang abbricht.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
Die folgenden Codefragmente in der Methode
ComputeFibonacci
berichten Fortschritt und Abbruchunterstützung.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
Prüfpunkt
An diesem Punkt können Sie die Anwendung Fibonacci Calculator kompilieren.
Drücken Sie F5, um die Anwendung zu kompilieren und auszuführen.
Während die Berechnung im Hintergrund ausgeführt wird, sehen Sie, dass ProgressBar den Fortschritt der Berechnung bis zum Abschluss anzeigt. Sie können auch ausstehenden Vorgang auch abbrechen.
Bei kleinen Zahlen sollte die Berechnung sehr schnell sein, aber bei größeren Zahlen sollten Sie eine merkliche Verzögerung feststellen. Wenn Sie einen Wert von 30 oder höher eingeben, sollten Sie, abhängig von Ihrem Computer, eine Verzögerung von mehreren Sekunden feststellen. Bei Werten, die höher als 40 sind, kann es Minuten oder Stunden dauern, die Berechnung abzuschließen. Beachten Sie, dass Sie das Formular frei verschieben, minimieren, maximieren und sogar schließen, während der Rechner eine hohe Fibonacci-Zahl berechnet. Dies liegt daran, dass der Haupt-UI-Thread nicht wartet, bis die Berechnung abgeschlossen ist.
Nächste Schritte
Nachdem Sie nun ein Formular implementiert haben, das eine BackgroundWorker-Komponente zur Ausführung einer Berechnung im Hintergrund verwendet, können Sie weitere Möglichkeiten für asynchrone Vorgänge erkunden:
Verwenden Sie mehrere BackgroundWorker-Objekte für verschiedene gleichzeitige Vorgänge.
Informationen zum Debuggen Ihrer Multithreadanwendung finden Sie unter Vorgehensweise: Verwenden des Fensters „Threads“.
Implementieren Sie eine eigene Komponente, die das asynchrone Programmiermodell unterstützt. Weitere Informationen finden Sie unter Übersicht über ereignisbasierte asynchrone Muster.
Achtung
Wenn Sie Multithreading verwenden, setzen Sie sich möglicherweise sehr ernsten und komplexen Problemen aus. Beachten Sie die Informationen unter Empfohlene Vorgehensweise für das verwaltete Threading, bevor Sie eine Projektmappe implementieren, die Multithreading verwendet.
Weitere Informationen
- System.ComponentModel.BackgroundWorker
- Verwaltetes Threading
- Empfohlene Vorgehensweise für das verwaltete Threading
- Event-based Asynchronous Pattern Overview (Übersicht über ereignisbasierte asynchrone Muster)
- How to: Implementieren eines Formulars, das eine Hintergrundoperation verwendet
- Exemplarische Vorgehensweise: Ausführen eines Vorgangs im Hintergrund
- BackgroundWorker-Komponente
.NET Desktop feedback