Поделиться через


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

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

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

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

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

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

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

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

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

Примечание

Отображаемые диалоговые окна и команды меню могут отличаться от описанных в справке в зависимости от текущих настроек или выпуска.Чтобы изменить параметры, выберите в меню Сервис пункт Импорт и экспорт параметров.Дополнительные сведения см. в разделе Работа с параметрами.

Создание проекта

Для начала следует создать проект и подготовить форму.

Чтобы создать форму, использующую фоновую операцию

  1. Создайте проект приложения Windows под названием BackgroundWorkerExample. Дополнительные сведения см. в разделе Практическое руководство. Создание проекта приложения Windows Forms.

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

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

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

  5. Переименуйте первый элемент управления Button startAsyncButton и установите свойство Text равным Start Async. Переименуйте второй элемент управления Button cancelAsyncButton и установите свойство Text равным Cancel Async. Установите для свойства Enabled значение false.

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

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

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

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

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

Чтобы создать BackgroundWorker с помощью конструктора

  • Со вкладки Компоненты панели элементов перетащите 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.
    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
    
    // 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.
    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;
    }
    
  3. В обработчике событий DoWork добавьте вызов метода ComputeFibonacci. Возьмите первый параметр ComputeFibonacci из свойства Argument DoWorkEventArgs. Параметры BackgroundWorker и DoWorkEventArgs будут использованы позднее для поддержки отчета о ходе выполнения и отмены. Присвойте значение, возвращенное из ComputeFibonacci, свойству Result DoWorkEventArgs. Результат будет доступен для обработчика событий RunWorkerCompleted.

    Примечание

    Обработчик события DoWork не ссылается на переменную экземпляра backgroundWorker1 напрямую, поскольку в этом случае обработчик будет связан с определенным экземпляром BackgroundWorker.Вместо этого ссылка на BackgroundWorker, создавший событие, берется из параметра sender.Это важно, если в форме размещено несколько BackgroundWorker.В обработчике событий DoWork нельзя выполнять действия ни с одним из объектов пользовательского интерфейса.Вместо этого следует взаимодействовать с пользовательским интерфейсом с помощью событий BackgroundWorker.

    ' 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 'backgroundWorker1_DoWork
    
    // 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,
    // 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 );
    }
    
  4. В обработчике события Click элемента управления startAsyncButton добавьте код, запускающий асинхронную операцию.

    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 
    
    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);
    }
    
    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 );
    }
    
  5. Назначьте результат вычислений средству управления resultLabel в обработчике событий RunWorkerCompleted.

    ' 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 'backgroundWorker1_RunWorkerCompleted
    
    // 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.
    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;
    }
    

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

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

Для внедрения отчета о ходе выполнения

  1. В окне Свойства выберите backgroundWorker1. Установите свойствам WorkerReportsProgress и WorkerSupportsCancellation значение true.

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

    Private numberToCompute As Integer = 0
    Private highestPercentageReached As Integer = 0
    
    private int numberToCompute = 0;
    private int highestPercentageReached = 0;
    
    int numberToCompute;
    int highestPercentageReached;
    
  3. Добавьте обработчик события для события ProgressChanged. В обработчике события ProgressChanged обновите ProgressBar со свойством ProgressPercentage параметра ProgressChangedEventArgs.

    ' 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
    
    // 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.
    void backgroundWorker1_ProgressChanged( Object^ /*sender*/, ProgressChangedEventArgs^ e )
    {
       this->progressBar1->Value = e->ProgressPercentage;
    }
    

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

  1. В обработчике события Click элемента управления cancelAsyncButton добавьте код, отменяющий асинхронную операцию.

    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 'cancelAsyncButton_Click
    
    private void cancelAsyncButton_Click(System.Object sender, 
        System.EventArgs e)
    {   
        // Cancel the asynchronous operation.
        this.backgroundWorker1.CancelAsync();
    
        // Disable the Cancel button.
        cancelAsyncButton.Enabled = false;
    }
    
    void cancelAsyncButton_Click( System::Object^ /*sender*/, System::EventArgs^ /*e*/ )
    {  
       // Cancel the asynchronous operation.
       this->backgroundWorker1->CancelAsync();
    
       // Disable the Cancel button.
       cancelAsyncButton->Enabled = false;
    }
    
  2. Следующие фрагменты кода в методе ComputeFibonacci сообщают о ходе выполнения и поддерживают отмену.

    If worker.CancellationPending Then
        e.Cancel = True
    
    
    ...
    
    
    ' 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
    
    if (worker.CancellationPending)
    {   
        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);
    }
    
    if ( worker->CancellationPending )
    {
       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 );
    }
    

Контрольная точка

Теперь можно скомпилировать и запустить приложение "Fibonacci Calculator".

Чтобы проверить проект

  • Нажмите клавишу F5, чтобы скомпилировать и запустить приложение.

    При выполнении вычислений в фоновом режиме вы увидите ProgressBar с отображением хода выполнения вычислений. Также можно отменить выполняющуюся операцию.

    Для небольших чисел вычисление будет выполняться очень быстро, но при вычислении больших чисел возникнут задержки. При вводе значения 20 или более задержка может составить несколько секунд (в зависимости от мощности компьютера). Для значений, превышающих 40, вычисление может занять от нескольких минут до нескольких часов. Обратите внимание, что пока калькулятор вычисляет большой число Фибоначчи, можно свободно перемещать форму, сворачивать ее, разворачивать и даже закрывать. Это возможно из-за того, что основной поток пользовательского интерфейса не дожидается завершения вычисления.

Следующие действия

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

См. также

Задачи

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

Пример. Фоновое выполнение операции

Ссылки

BackgroundWorker

Основные понятия

Рекомендации по работе с потоками

Другие ресурсы

Многопоточность в компонентах

Multithreading in Visual Basic

Компонент BackgroundWorker