Wskazówki: wdrażanie formularza korzystającego z operacji w tle

Jeśli masz operację, która potrwa długo i nie chcesz, aby interfejs użytkownika przestał odpowiadać lub blokować, możesz użyć BackgroundWorker klasy , aby wykonać operację w innym wątku.

W tym przewodniku pokazano, jak używać BackgroundWorker klasy do wykonywania czasochłonnych obliczeń "w tle", podczas gdy interfejs użytkownika pozostaje dynamiczny. Po zakończeniu będziesz mieć aplikację, która oblicza asynchronicznie liczby Fibonacciego. Mimo że przetwarzanie dużej liczby Fibonacciego może zająć zauważalną ilość czasu, główny wątek interfejsu użytkownika nie zostanie przerwany przez to opóźnienie, a formularz będzie odpowiadać podczas obliczeń.

Zadania przedstawione w tym przewodniku obejmują:

  • Tworzenie aplikacji opartej na systemie Windows

  • Tworzenie obiektu BackgroundWorker w formularzu

  • Dodawanie asynchronicznych programów obsługi zdarzeń

  • Dodawanie raportowania postępu i obsługi anulowania

Aby uzyskać pełną listę kodu używanego w tym przykładzie, zobacz How to: Implement a Form That Uses a Background Operation (Instrukcje: implementowanie formularza używającego operacji w tle).

Tworzenie formularza korzystającego z operacji w tle

  1. W programie Visual Studio utwórz projekt aplikacji oparty na systemie Windows o nazwie BackgroundWorkerExample (File>New>Project>Visual C# lub Visual Basic>Classic Desktop>Windows Forms Application).

  2. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy pozycję Form1 i wybierz polecenie Zmień nazwę z menu skrótów. Zmień nazwę pliku na FibonacciCalculator. Kliknij przycisk Tak po wyświetleniu monitu o zmianę nazwy wszystkich odwołań do elementu kodu "Form1".

  3. Przeciągnij kontrolkę NumericUpDown z przybornika na formularz. Minimum Ustaw właściwość na 1 , a Maximum właściwość na 91.

  4. Dodaj dwie Button kontrolki do formularza.

  5. Zmień nazwę pierwszej Button kontrolki startAsyncButtonText i ustaw właściwość na Start Async. Zmień nazwę drugiej Button kontrolki cancelAsyncButtoni ustaw Text właściwość na Cancel Async. Ustaw jej Enabled właściwość na false.

  6. Utwórz procedurę obsługi zdarzeń dla obu zdarzeń Button kontrolek Click . Aby uzyskać szczegółowe informacje, zobacz How to: Create Event Handlers Using the Projektant (Instrukcje: tworzenie procedur obsługi zdarzeń przy użyciu Projektant).

  7. Przeciągnij kontrolkę Label z przybornika na formularz i zmień jego resultLabelnazwę .

  8. Przeciągnij kontrolkę ProgressBar z przybornika na formularz.

Tworzenie aplikacji BackgroundWorker za pomocą Projektant

Element dla operacji asynchronicznej można utworzyć BackgroundWorker przy użyciu Projektant WindowsForms.

Na karcie Składniki przybornika przeciągnij element BackgroundWorker na formularz.

Dodawanie asynchronicznych procedur obsługi zdarzeń

Teraz możesz dodać programy obsługi zdarzeń dla BackgroundWorker zdarzeń asynchronicznych składnika. Czasochłonna operacja, która zostanie uruchomiona w tle, która oblicza liczby Fibonacciego, jest wywoływana przez jedną z tych procedur obsługi zdarzeń.

  1. W oknie Właściwości z nadal wybranym składnikiem BackgroundWorker kliknij przycisk Zdarzenia. Kliknij dwukrotnie zdarzenia iRunWorkerCompleted, DoWork aby utworzyć programy obsługi zdarzeń. Aby uzyskać więcej informacji o sposobie korzystania z programów obsługi zdarzeń, zobacz How to: Create Event Handlers Using the Projektant (Instrukcje: tworzenie programów obsługi zdarzeń przy użyciu Projektant).

  2. Utwórz nową metodę o nazwie ComputeFibonacci, w formularzu. Ta metoda wykonuje rzeczywistą pracę i zostanie uruchomiona w tle. Ten kod demonstruje rekursywną implementację algorytmu Fibonacciego, który jest szczególnie nieefektywny, biorąc wykładniczo dłuższy czas na ukończenie dla większych liczb. Jest on używany w tym miejscu do celów ilustracyjnych, aby pokazać operację, która może wprowadzać długie opóźnienia w aplikacji.

    // 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. W procedurze obsługi zdarzeń DoWork dodaj wywołanie metody ComputeFibonacci . Użyj pierwszego parametru dla elementu ComputeFibonacci z właściwości DoWorkEventArgs.Argument Parametry BackgroundWorker i DoWorkEventArgs będą później używane do raportowania postępu i obsługi anulowania. Przypisz wartość zwracaną z ComputeFibonacci do Result właściwości DoWorkEventArgs. Ten wynik będzie dostępny dla programu obsługi zdarzeń RunWorkerCompleted .

    Uwaga

    Procedura DoWork obsługi zdarzeń nie odwołuje się bezpośrednio do backgroundWorker1 zmiennej wystąpienia, ponieważ spowoduje to połączenie tej procedury obsługi zdarzeń do określonego BackgroundWorkerwystąpienia programu . Zamiast tego odwołanie do BackgroundWorker tego, które wywołało to zdarzenie, zostanie odzyskane z parametru sender . Jest to ważne, gdy formularz jest hostem więcej niż jednego BackgroundWorker. Ważne jest również, aby nie manipulować żadnymi obiektami interfejsu użytkownika w DoWork programie obsługi zdarzeń. Zamiast tego komunikują się z interfejsem użytkownika za pośrednictwem zdarzeń 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. W procedurze startAsyncButton obsługi zdarzeń kontrolki Click dodaj kod, który uruchamia operację asynchroniczną.

    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. W procedurze obsługi zdarzeń RunWorkerCompleted przypisz wynik obliczenia do kontrolki 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
    

Dodawanie raportowania postępu i obsługi anulowania

W przypadku operacji asynchronicznych, które trwają długo, często pożądane jest zgłaszanie postępu dla użytkownika i umożliwienie użytkownikowi anulowania operacji. Klasa BackgroundWorker udostępnia zdarzenie, które umożliwia publikowanie postępu w miarę kontynuowania operacji w tle. Udostępnia również flagę umożliwiającą kodowi procesu roboczego wykrywanie wywołania CancelAsync i przerywanie działania.

Implementowanie raportowania postępu

  1. W oknie Właściwości wybierz pozycję backgroundWorker1. WorkerReportsProgress Ustaw właściwości i WorkerSupportsCancellation na true.

  2. Zadeklaruj dwie zmienne w formularzu FibonacciCalculator . Będą one używane do śledzenia postępu.

    int numberToCompute;
    int highestPercentageReached;
    
    private int numberToCompute = 0;
    private int highestPercentageReached = 0;
    
    Private numberToCompute As Integer = 0
    Private highestPercentageReached As Integer = 0
    
  3. Dodaj procedurę obsługi zdarzeń dla ProgressChanged zdarzenia. W procedurze obsługi zdarzeń ProgressChanged zaktualizuj element ProgressBar za ProgressPercentage pomocą właściwości parametru 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
    

Implementowanie obsługi anulowania

  1. W procedurze cancelAsyncButton obsługi zdarzeń kontrolki Click dodaj kod, który anuluje operację asynchroniczną.

    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. Poniższe fragmenty kodu w postępie ComputeFibonacci raportu metody i obsługują anulowanie.

    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
    

Punkt kontrolny

Na tym etapie można skompilować i uruchomić aplikację Fibonacci Calculator.

Naciśnij klawisz F5 , aby skompilować i uruchomić aplikację.

Podczas wykonywania obliczeń w tle zobaczysz ProgressBar postęp obliczeń w kierunku ukończenia. Możesz również anulować oczekującą operację.

W przypadku małych liczb obliczenie powinno być bardzo szybkie, ale w przypadku większych liczb powinno być widoczne zauważalne opóźnienie. Jeśli wprowadzisz wartość 30 lub większą, powinno zostać wyświetlone opóźnienie kilku sekund, w zależności od szybkości komputera. W przypadku wartości większych niż 40 może upłynąć kilka minut lub godzin, aby zakończyć obliczanie. Chociaż kalkulator jest zajęty przetwarzaniem dużej liczby Fibonacciego, zwróć uwagę, że można swobodnie przenosić formularz wokół, zminimalizować, zmaksymalizować, a nawet odrzucić go. Dzieje się tak, ponieważ główny wątek interfejsu użytkownika nie czeka na zakończenie obliczeń.

Następne kroki

Po zaimplementowaniu formularza, który używa BackgroundWorker składnika do wykonywania obliczeń w tle, możesz zapoznać się z innymi możliwościami operacji asynchronicznych:

  • Używaj wielu BackgroundWorker obiektów do wykonywania kilku równoczesnych operacji.

  • Aby debugować aplikację wielowątkową, zobacz Instrukcje: korzystanie z okna Wątki.

  • Zaimplementuj własny składnik obsługujący model programowania asynchronicznego. Aby uzyskać więcej informacji, zobacz Omówienie wzorca asynchronicznego opartego na zdarzeniach.

    Uwaga

    W przypadku korzystania z wielowątku dowolnego rodzaju potencjalnie narażasz się na bardzo poważne i złożone błędy. Przed zaimplementowaniem dowolnego rozwiązania korzystającego z wielowątku zapoznaj się z najlepszymi rozwiązaniami dotyczącymi wątków zarządzanych.

Zobacz też