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
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).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
".Przeciągnij kontrolkę NumericUpDown z przybornika na formularz. Minimum Ustaw właściwość na
1
, a Maximum właściwość na91
.Dodaj dwie Button kontrolki do formularza.
Zmień nazwę pierwszej Button kontrolki
startAsyncButton
Text i ustaw właściwość naStart Async
. Zmień nazwę drugiej Button kontrolkicancelAsyncButton
i ustaw Text właściwość naCancel Async
. Ustaw jej Enabled właściwość nafalse
.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 Designer (Instrukcje: tworzenie procedur obsługi zdarzeń przy użyciu projektanta).
Przeciągnij kontrolkę Label z przybornika na formularz i zmień jego
resultLabel
nazwę .Przeciągnij kontrolkę ProgressBar z przybornika na formularz.
Tworzenie elementu BackgroundWorker za pomocą projektanta
Element dla operacji asynchronicznej można utworzyć BackgroundWorker przy użyciu narzędzia Windows Forms Designer.
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ń.
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 na temat korzystania z programów obsługi zdarzeń, zobacz Instrukcje: tworzenie programów obsługi zdarzeń przy użyciu projektanta.
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
W procedurze obsługi zdarzeń DoWork dodaj wywołanie metody
ComputeFibonacci
. Użyj pierwszego parametru dla elementuComputeFibonacci
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ą zComputeFibonacci
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 parametrusender
. 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
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
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
W oknie Właściwości wybierz pozycję
backgroundWorker1
. WorkerReportsProgress Ustaw właściwości i WorkerSupportsCancellation natrue
.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
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
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
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 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ż
- System.ComponentModel.BackgroundWorker
- Zarządzane wątkowanie
- Zarządzana wątkowość — najlepsze rozwiązania
- Asynchroniczny wzorzec oparty na zdarzeniach — omówienie
- Instrukcje: implementowanie formularza korzystającego z operacji w tle
- Przewodnik: przeprowadzanie operacji w tle
- BackgroundWorker, składnik
.NET Desktop feedback