Share via


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

  1. 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).

  2. 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 Codeelement Form1 umbenannt werden sollen.

  3. Ziehen Sie ein NumericUpDown-Steuerelement von der Toolbox auf das Formular. Legen Sie die Minimum-Eigenschaft auf 1 und die Maximum-Eigenschaft auf 91 fest.

  4. Fügen Sie dem Formular zwei Button-Steuerelemente hinzu.

  5. Geben Sie dem ersten Button-Steuerelement den Namen startAsyncButton, und legen Sie die Eigenschaft Text auf Start Async fest. Geben Sie dem zweiten Button-Steuerelement den Namen cancelAsyncButton, und legen Sie die Eigenschaft Text auf Cancel Async fest. Legen Sie die zugehörige Enabled-Eigenschaft auf false fest.

  6. Erstellen Sie einen Ereignishandler für beide Button-Ereignisse des Click- Steuerelements. Weitere Informationen finden Sie unter Vorgehensweise: Erstellen von Ereignishandlern mithilfe des Designers.

  7. Ziehen Sie ein Label-Steuerelement aus der Toolbox auf das Formular, und benennen Sie es in resultLabel um.

  8. 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.

  1. Klicken Sie im Fenster Eigenschaften auf die Schaltfläche Ereignisse, während die Komponente BackgroundWorker 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.

  2. 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
    
  3. Fügen Sie im Ereignishandler DoWork einen Aufruf der Methode ComputeFibonacci hinzu. Verwenden Sie den ersten Parameter für ComputeFibonacci 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 von ComputeFibonacci 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 dem sender-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
    
  4. Fügen Sie dem Click-Ereignishandler des startAsyncButton-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 
    
  5. 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

  1. Wählen Sie im Fenster EigenschaftenbackgroundWorker1 aus. Legen Sie für die Eigenschaften WorkerReportsProgress (Breite) und WorkerSupportsCancellation (Höhe) den Wert true fest.

  2. 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
    
  3. 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

  1. Fügen Sie dem Click-Ereignishandler des cancelAsyncButton-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
    
  2. 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:

Weitere Informationen