Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Если какая-либо операция будет выполняться в течение долгого времени, и при этом нельзя допустить, чтобы пользовательский интерфейс перестал отвечать или завис, можно использовать класс 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.Создайте обработчик событий для событий обоих элементов управления ButtonClick. Дополнительные сведения см. в разделе Практическое руководство. Создание обработчика событий с помощью конструктора.
Перетащите элемент управления 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свойству Result объекта DoWorkEventArgs. Этот результат будет доступен обработчику событий RunWorkerCompleted.Замечание
Обработчик событий DoWork не ссылается на переменную экземпляра
backgroundWorker1напрямую, так как это связало бы данный обработчик событий с конкретным экземпляром BackgroundWorker. Вместо этого ссылка на BackgroundWorker, вызвавшего это событие, извлекается из параметраsender. Это важно, когда в форме есть более чем один 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В обработчике событий
startAsyncButtonэлемента управления Click добавьте код, запускающий асинхронную операцию.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
Внедрение поддержки отмены
В обработчике событий
cancelAsyncButtonэлемента управления Click добавьте код, отменяющий асинхронную операцию.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