Пошаговое руководство. Реализация формы, в которой выполняется фоновая операция

Если какая-либо операция будет выполняться в течение долгого времени, и при этом нельзя допустить, чтобы пользовательский интерфейс перестал отвечать или завис, можно использовать класс BackgroundWorker для выполнения этой операции в другом потоке.

В этом пошаговом руководстве показано, как использовать класс BackgroundWorker для выполнения длительных вычислений в фоновом режиме, не мешающем работе пользовательского интерфейса. По завершении работы будет создано приложение, вычисляющее числа Фибоначчи в асинхронном режиме. Несмотря на то что вычисление крупных чисел Фибоначчи может занимать много времени, основной поток пользовательского интерфейса в результате этой задержки прерываться не будет, а форма в ходе вычисления останется рабочей.

В данном пошаговом руководстве представлены следующие задачи.

  • Создание приложения на базе Windows

  • Создание BackgroundWorker в вашей форме

  • Добавление обработчиков асинхронных событий

  • Добавление отчетов о ходе выполнения и поддержка отмены

Полный код, используемый в этом примере, см. в разделе Практическое руководство. Реализация формы, в которой выполняется фоновая операция.

Создание формы, в которой используется фоновая операция

  1. В Visual Studio создайте проект приложения на основе Windows под названием BackgroundWorkerExample (Файл >Создать >Проект >Visual C# или Visual Basic >Классическое >Приложение Windows Forms).

  2. В обозревателе решений щелкните правой кнопкой мыши Form1 и выберите в контекстном меню команду Переименовать. Измените имя файла на FibonacciCalculator. Чтобы переименовать все ссылки на элемент кода 'Form1', в соответствующем запросе нажмите кнопку Да.

  3. Перетащите элемент управления NumericUpDown из панели элементов в форму. Задайте для свойства Minimum значение 1, а для свойства Maximum — значение 91.

  4. Добавьте в форму два элемента управления Button.

  5. Переименуйте первый элемент управления ButtonstartAsyncButton и задайте для свойства Text значение Start Async. Переименуйте второй элемент управления ButtoncancelAsyncButton и задайте для свойства Text значение Cancel Async. Для его свойства Enabled задайте значение false.

  6. Создайте обработчик событий для обоих событий Click элементов управления Button. Дополнительные сведения см. в разделе Практическое руководство. Создание обработчика событий с помощью конструктора.

  7. Перетащите элемент управления Label из панели элементов в форму и измените его имя на resultLabel.

  8. Перетащите элемент управления ProgressBar из панели элементов в форму.

Создание BackgroundWorker с помощью конструктора

Вы можете создать BackgroundWorker для асинхронной операции с помощью конструктора WindowsForms.

Со вкладки Компонентыпанели элементов перетащите BackgroundWorker в форму.

Добавление обработчиков асинхронных событий

Теперь вы можете добавить обработчики событий для асинхронных событий компонента BackgroundWorker. Один из этих обработчиков вызывает длительную операцию, вычисляющую числа Фибоначчи в фоновом режиме.

  1. Выбрав компонент BackgroundWorker, в окне Свойства нажмите кнопку События. Дважды щелкните события DoWork и RunWorkerCompleted, чтобы создать обработчики событий. Дополнительные сведения о том, как использовать обработчики событий, см. в разделе Практическое руководство. Создание обработчиков событий с помощью конструктора.

  2. Создайте в форме новый метод с именем 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
    
  3. В обработчике событий 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
    
  4. В обработчике событий 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 
    
  5. В обработчике событий 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 и прерывать себя.

Реализация отчетов о ходе выполнения

  1. В окне Свойства выберите backgroundWorker1. Задайте для свойств WorkerReportsProgress и WorkerSupportsCancellation значение true.

  2. Объявите две переменные в форме FibonacciCalculator. Они будут использоваться для отслеживания хода выполнения.

    int numberToCompute;
    int highestPercentageReached;
    
    private int numberToCompute = 0;
    private int highestPercentageReached = 0;
    
    Private numberToCompute As Integer = 0
    Private highestPercentageReached As Integer = 0
    
  3. Добавьте обработчик для события 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
    

Реализация поддержки отмены

  1. В обработчике событий 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
    
  2. Следующие фрагменты кода в методе 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, и можете изучить другие возможности для асинхронных операций.

См. также