Пошаговое руководство. Реализация формы, в которой выполняется фоновая операция
Если какая-либо операция будет выполняться в течение долгого времени, и при этом нельзя допустить, чтобы пользовательский интерфейс перестал отвечать или завис, можно использовать класс BackgroundWorker для выполнения этой операции в другом потоке.
В этом пошаговом руководстве показано, как использовать класс BackgroundWorker для выполнения длительных вычислений в фоновом режиме, не мешающем работе пользовательского интерфейса. По завершении работы будет создано приложение, вычисляющее числа Фибоначчи в асинхронном режиме. Несмотря на то что вычисление крупных чисел Фибоначчи может занимать много времени, основной поток пользовательского интерфейса в результате этой задержки прерываться не будет, а форма в ходе вычисления останется рабочей.
В данном пошаговом руководстве представлены следующие задачи.
Создание приложения на базе Windows
Создание BackgroundWorker в вашей форме
Добавление обработчиков асинхронных событий
Добавление отчетов о ходе выполнения и поддержка отмены
Полный код, используемый в этом примере, см. в разделе Практическое руководство. Реализация формы, в которой выполняется фоновая операция.
Создание формы, в которой используется фоновая операция
В Visual Studio создайте проект приложения на основе Windows под названием
BackgroundWorkerExample
(Файл >Создать >Проект >Visual C# или Visual Basic >Классическое >Приложение Windows Forms).В обозревателе решений щелкните правой кнопкой мыши Form1 и выберите в контекстном меню команду Переименовать. Измените имя файла на
FibonacciCalculator
. Чтобы переименовать все ссылки на элемент кода 'Form1
', в соответствующем запросе нажмите кнопку Да.Перетащите элемент управления NumericUpDown из панели элементов в форму. Задайте для свойства Minimum значение
1
, а для свойства Maximum — значение91
.Добавьте в форму два элемента управления Button.
Переименуйте первый элемент управления Button
startAsyncButton
и задайте для свойства Text значениеStart Async
. Переименуйте второй элемент управления ButtoncancelAsyncButton
и задайте для свойства Text значениеCancel Async
. Для его свойства Enabled задайте значениеfalse
.Создайте обработчик событий для обоих событий Click элементов управления Button. Дополнительные сведения см. в разделе Практическое руководство. Создание обработчика событий с помощью конструктора.
Перетащите элемент управления Label из панели элементов в форму и измените его имя на
resultLabel
.Перетащите элемент управления ProgressBar из панели элементов в форму.
Создание BackgroundWorker с помощью конструктора
Вы можете создать BackgroundWorker для асинхронной операции с помощью конструктора WindowsForms.
Со вкладки Компонентыпанели элементов перетащите BackgroundWorker в форму.
Добавление обработчиков асинхронных событий
Теперь вы можете добавить обработчики событий для асинхронных событий компонента BackgroundWorker. Один из этих обработчиков вызывает длительную операцию, вычисляющую числа Фибоначчи в фоновом режиме.
Выбрав компонент BackgroundWorker, в окне Свойства нажмите кнопку События. Дважды щелкните события DoWork и RunWorkerCompleted, чтобы создать обработчики событий. Дополнительные сведения о том, как использовать обработчики событий, см. в разделе Практическое руководство. Создание обработчиков событий с помощью конструктора.
Создайте в форме новый метод с именем
ComputeFibonacci
. Этот метод выполняет фактическую работу и делает это в фоновом режиме. Данный код демонстрирует рекурсивную реализацию алгоритма Фибоначчи — она довольно неэффективна и занимает в разы больше времени при работе с большими числами. Он приводится в иллюстративных целях, чтобы показать, что операция может значительно замедлить работу вашего приложения.// This is the method that does the actual work. For this // example, it computes a Fibonacci number and // reports progress as it does its work. 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
В обработчике событий DoWork добавьте вызов метода
ComputeFibonacci
. Возьмите первый параметр дляComputeFibonacci
из свойства ArgumentDoWorkEventArgs. Параметры BackgroundWorker и DoWorkEventArgs будут использоваться позднее для отчетов о ходе выполнения и поддержки отмены. Назначьте возвращаемое значение изComputeFibonacci
свойству ResultDoWorkEventArgs. Этот результат будет доступен обработчику событий RunWorkerCompleted.Примечание.
Обработчик событий DoWork не ссылается на переменную экземпляра
backgroundWorker1
напрямую, так как это связало бы данный обработчик событий с конкретным экземпляром BackgroundWorker. Вместо этого ссылка наsender
, вызывающий это событие, извлекается из параметра BackgroundWorker. Это важно, если в форме несколько BackgroundWorker. Также важно не работать с какими-либо объектами пользовательского интерфейса в обработчике событий DoWork. Вместо этого следует взаимодействовать с пользовательским интерфейсом с помощью событий 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
В обработчике событий Click элемента управления
startAsyncButton
добавьте код, запускающий асинхронную операцию.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
В обработчике событий RunWorkerCompleted назначьте результат вычисления элементу управления
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
Добавление отчетов о ходе выполнения и поддержка отмены
В случае асинхронных операций, занимающих долгое время, желательно сообщать пользователю о ходе выполнения, чтобы пользователь мог отменить операцию. Класс BackgroundWorker предоставляет событие, позволяющее отображать ход выполнения фоновой операции. Он также предоставляет флаг, позволяющий вашему рабочему коду обнаруживать вызов CancelAsync и прерывать себя.
Реализация отчетов о ходе выполнения
В окне Свойства выберите
backgroundWorker1
. Задайте для свойств WorkerReportsProgress и WorkerSupportsCancellation значениеtrue
.Объявите две переменные в форме
FibonacciCalculator
. Они будут использоваться для отслеживания хода выполнения.int numberToCompute; int highestPercentageReached;
private int numberToCompute = 0; private int highestPercentageReached = 0;
Private numberToCompute As Integer = 0 Private highestPercentageReached As Integer = 0
Добавьте обработчик для события ProgressChanged. В обработчике событий ProgressChanged обновите ProgressBar с помощью свойства ProgressPercentage параметра 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
Реализация поддержки отмены
В обработчике событий Click элемента управления
cancelAsyncButton
добавьте код, отменяющий асинхронную операцию.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
Следующие фрагменты кода в методе
ComputeFibonacci
сообщают о ходе выполнения и поддерживают отмену.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
Контрольная точка
На этом этапе можно скомпилировать и запустить приложение калькулятора Фибоначчи.
Чтобы скомпилировать и запустить приложение, нажмите клавишу F5.
Во время вычисления в фоновом режиме вы будете видеть ProgressBar, где будет отображаться ход выполнения вычисления до его завершения. Операцию, ожидающую выполнения, можно отменить.
Если числа небольшие, вычисления выполняются быстро, а с крупными числами занимают заметно больше времени. Если ввести значение 30 или больше, задержка составит несколько секунд, в зависимости от скорости работы вашего компьютера. Для значений больше 40 вычисление может затянуться на несколько часов. Пока калькулятор вычисляет большое число Фибоначчи, форму можно свободно переместить, свернуть, развернуть и даже закрыть. Это связано с тем, что основной поток пользовательского интерфейса не дожидается, пока вычисление будет завершено.
Следующие шаги
Теперь вы реализовали форму, в которой для вычисления в фоновом режиме используется компонент BackgroundWorker, и можете изучить другие возможности для асинхронных операций.
Использование нескольких объектов BackgroundWorker для нескольких одновременных операций.
Отладка многопоточных приложений (см. раздел Практическое руководство. Использование окна потоков).
Реализация собственного компонента, поддерживающего модель асинхронного программирования. Дополнительные сведения см. в разделе Обзор асинхронной модели на основе событий.
Внимание
При использовании любой многопоточности существует потенциальная возможность возникновения серьезных ошибок. Перед реализацией любого решения, в котором используется многопоточность, ознакомьтесь с разделом Рекомендации по работе с потоками.
См. также
.NET Desktop feedback
Обратная связь
https://aka.ms/ContentUserFeedback.
Ожидается в ближайшее время: в течение 2024 года мы постепенно откажемся от GitHub Issues как механизма обратной связи для контента и заменим его новой системой обратной связи. Дополнительные сведения см. в разделеОтправить и просмотреть отзыв по