Partilhar via


Passo a passo: Implementando um formulário que usa uma operação em segundo plano

Se você tiver uma operação que levará muito tempo para ser concluída e não quiser que a interface do usuário (UI) pare de responder ou bloqueie, você pode usar a BackgroundWorker classe para executar a operação em outro thread.

Este passo a passo ilustra como usar a BackgroundWorker classe para executar cálculos demorados "em segundo plano", enquanto a interface do usuário permanece responsiva. Quando terminar, você terá um aplicativo que calcula os números de Fibonacci de forma assíncrona. Embora a computação de um grande número de Fibonacci possa levar uma quantidade notável de tempo, o thread principal da interface do usuário não será interrompido por esse atraso e o formulário responderá durante o cálculo.

As tarefas ilustradas neste passo a passo incluem:

  • Criando um aplicativo baseado no Windows

  • Criando um BackgroundWorker no seu formulário

  • Adicionando manipuladores de eventos assíncronos

  • Adicionando relatórios de progresso e suporte para cancelamento

Para obter uma lista completa do código usado neste exemplo, consulte Como implementar um formulário que usa uma operação em segundo plano.

Criar um formulário que usa uma operação em segundo plano

  1. No Visual Studio, crie um projeto de aplicativo baseado no Windows chamado BackgroundWorkerExample (File>New>Project>Visual C# ou Visual Basic>Classic Desktop>Windows Forms Application).

  2. No Gerenciador de Soluções, clique com o botão direito do mouse em Form1 e selecione Renomear no menu de atalho. Altere o nome do arquivo para FibonacciCalculator. Clique no botão Sim quando lhe for perguntado se pretende mudar o nome de todas as referências para o elemento de código 'Form1'.

  3. Arraste um NumericUpDown controle da Caixa de Ferramentas para o formulário. Defina a Minimum propriedade como 1 e a Maximum propriedade como 91.

  4. Adicione dois controles Button ao formulário.

  5. Renomeie o primeiro Button controle startAsyncButton e defina a Text propriedade como Start Async. Renomeie o segundo Button controle cancelAsyncButtone defina a Text propriedade como Cancel Async. Defina a propriedade Enabled como false.

  6. Crie um manipulador de eventos para os eventos de ambos os controles ButtonClick. Para obter detalhes, consulte Como criar manipuladores de eventos usando o Designer.

  7. Arraste um Label controle da Caixa de Ferramentas para o formulário e renomeie-o resultLabel.

  8. Arraste um ProgressBar controle da Caixa de Ferramentas para o formulário.

Criar um BackgroundWorker com o Designer

Você pode criar o BackgroundWorker para a sua operação assíncrona usando o WindowsForms Designer.

Na guia Componentes da Caixa de Ferramentas, arraste a BackgroundWorker para o formulário.

Adicionar manipuladores de eventos assíncronos

Agora você está pronto para adicionar manipuladores de eventos para os eventos assíncronos BackgroundWorker do componente. A operação demorada que será executada em segundo plano, que calcula os números de Fibonacci, é chamada por um desses manipuladores de eventos.

  1. Na janela Propriedades , com o BackgroundWorker componente ainda selecionado, clique no botão Eventos . Clique duas vezes nos DoWork eventos e RunWorkerCompleted para criar manipuladores de eventos. Para obter mais informações sobre como usar manipuladores de eventos, consulte Como criar manipuladores de eventos usando o Designer.

  2. Crie um novo método, chamado ComputeFibonacci, em seu formulário. Esse método faz o trabalho real e será executado em segundo plano. Este código demonstra a implementação recursiva do algoritmo de Fibonacci, que é notavelmente ineficiente, levando exponencialmente mais tempo para ser concluído para números maiores. Ele é usado aqui para fins ilustrativos, para mostrar uma operação que pode introduzir longos atrasos em sua aplicação.

    // 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. No manipulador de DoWork eventos, adicione uma chamada ao ComputeFibonacci método. Tome o primeiro parâmetro para ComputeFibonacci da propriedade Argument do DoWorkEventArgs. Os BackgroundWorker parâmetros e DoWorkEventArgs serão usados posteriormente para relatórios de progresso e suporte a cancelamentos. Atribua o valor de retorno de ComputeFibonacci à Result propriedade do DoWorkEventArgs. Esse resultado estará disponível para o manipulador de RunWorkerCompleted eventos.

    Observação

    O DoWork manipulador de eventos não faz referência direta à backgroundWorker1 variável de instância, pois isso associaria esse manipulador de eventos a uma instância específica de BackgroundWorker. Em vez disso, uma referência ao BackgroundWorker que gerou esse evento é recuperada do sender parâmetro. Isso é importante quando o formulário contém mais de um BackgroundWorker. Também é importante não manipular nenhum objeto de interface do usuário em seu DoWork manipulador de eventos. Em vez disso, comunique-se com a interface do usuário por meio dos BackgroundWorker eventos.

    // 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. startAsyncButton No manipulador de Click eventos do controle, adicione o código que inicia a operação assíncrona.

    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 No manipulador de eventos, atribua o resultado do cálculo ao resultLabel controle.

    // 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
    

Adicionando relatórios de progresso e suporte para cancelamento

Para operações assíncronas que levarão muito tempo, geralmente é desejável relatar o progresso para o usuário e permitir que o usuário cancele a operação. A BackgroundWorker classe fornece um evento que permite que você publique o progresso à medida que sua operação em segundo plano prossegue. Ele também fornece um sinalizador que permite que o seu código de trabalho detete uma chamada para CancelAsync e se interrompa.

Implementar relatórios de progresso

  1. Na janela Propriedades, selecione backgroundWorker1. Defina as WorkerReportsProgress propriedades e WorkerSupportsCancellation como true.

  2. Declare duas variáveis no FibonacciCalculator formulário. Estes serão utilizados para acompanhar os progressos.

    int numberToCompute;
    int highestPercentageReached;
    
    private int numberToCompute = 0;
    private int highestPercentageReached = 0;
    
    Private numberToCompute As Integer = 0
    Private highestPercentageReached As Integer = 0
    
  3. Adicione um manipulador de eventos para o ProgressChanged evento. No manipulador de eventos ProgressChanged, atualize ProgressBar utilizando a propriedade ProgressPercentage do parâmetro 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
    

Implementar suporte para cancelamento

  1. cancelAsyncButton No manipulador de Click eventos do controle, adicione o código que cancela a operação assíncrona.

    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. Os fragmentos de código a seguir no método ComputeFibonacci relatam o progresso e suportam o cancelamento.

    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
    

Ponto de controlo

Neste ponto, você pode compilar e executar o aplicativo Fibonacci Calculator.

Pressione F5 para compilar e executar o aplicativo.

Enquanto o cálculo estiver sendo executado em segundo plano, você verá a ProgressBar exibição do progresso do cálculo em direção à conclusão. Você também pode cancelar a operação pendente.

Para números pequenos, o cálculo deve ser muito rápido, mas para números maiores, você deve ver um atraso percetível. Se introduzir um valor igual ou superior a 30, deverá ver um atraso de vários segundos, dependendo da velocidade do seu computador. Para valores superiores a 40, pode demorar minutos ou horas a concluir o cálculo. Enquanto a calculadora está ocupada computando um grande número de Fibonacci, observe que você pode mover livremente o formulário, minimizá-lo, maximizá-lo e até mesmo descartá-lo. Isso ocorre porque o thread principal da interface do usuário não está aguardando a conclusão do cálculo.

Próximos passos

Agora que você implementou um formulário que usa um BackgroundWorker componente para executar um cálculo em segundo plano, você pode explorar outras possibilidades para operações assíncronas:

  • Use vários BackgroundWorker objetos para várias operações simultâneas.

  • Para depurar seu aplicativo multithreaded, consulte Como usar a janela Threads.

  • Implemente seu próprio componente que suporte o modelo de programação assíncrona. Para obter mais informações, consulte Visão geral do padrão assíncrono baseado em eventos.

    Atenção

    Ao usar multithreading de qualquer tipo, você potencialmente se expõe a bugs muito sérios e complexos. Consulte as Práticas Recomendadas de Threading Gerenciado antes de implementar qualquer solução que use multithreading.

Ver também