Partager via


Procédure pas à pas : implémentation d’un formulaire qui utilise une opération en arrière-plan

Si vous avez une opération qui prendra beaucoup de temps et que vous ne souhaitez pas que votre interface utilisateur cesse de répondre ou de bloquer, vous pouvez utiliser la BackgroundWorker classe pour exécuter l’opération sur un autre thread.

Cette procédure pas à pas montre comment utiliser la BackgroundWorker classe pour effectuer des calculs chronophages « en arrière-plan », tandis que l’interface utilisateur reste réactive. Lorsque vous aurez terminé, vous aurez une application qui calcule les nombres de Fibonacci de manière asynchrone. Même si le calcul d’un grand nombre Fibonacci peut prendre un certain temps, le thread d’interface utilisateur principal ne sera pas interrompu par ce délai et le formulaire sera réactif pendant le calcul.

Les tâches illustrées dans cette procédure pas à pas sont les suivantes :

  • Création d’une application Windows

  • Création d’un BackgroundWorker dans votre formulaire

  • Ajout de gestionnaires d’événements asynchrones

  • Ajout de rapports de progression et prise en charge de l’annulation

Pour obtenir une liste complète du code utilisé dans cet exemple, consultez Guide pratique pour implémenter un formulaire qui utilise une opération en arrière-plan.

Créer un formulaire qui utilise une opération d’arrière-plan

  1. Dans Visual Studio, créez un projet d’application Windows appelé BackgroundWorkerExample (File>New>Project>Visual C# ou Visual Basic>Classic Desktop>Application Windows Forms).

  2. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur Form1 et sélectionnez Renommer dans le menu contextuel. Remplacez le nom de fichier par FibonacciCalculator. Cliquez sur le bouton Oui lorsque vous êtes invité à renommer toutes les références à l’élément de code «Form1 ».

  3. Faites glisser un NumericUpDown contrôle de la boîte à outils sur le formulaire. Définissez la Minimum propriété sur 1 et la Maximum propriété sur 91.

  4. Ajoutez deux contrôles Button au formulaire.

  5. Renommez le premier Button contrôle startAsyncButton et définissez la Text propriété sur Start Async. Renommez le deuxième Button contrôle cancelAsyncButtonet définissez la Text propriété sur Cancel Async. Définissez sa propriété Enabled sur false.

  6. Créez un gestionnaire d'événements pour les événements des deux contrôles ButtonClick. Pour plus d’informations, consultez Guide pratique pour créer des gestionnaires d’événements à l’aide du concepteur.

  7. Faites glisser un Label contrôle de la boîte à outils sur le formulaire et renommez-le resultLabel.

  8. Faites glisser un ProgressBar contrôle de la boîte à outils sur le formulaire.

Créer un BackgroundWorker avec le Concepteur

Vous pouvez créer le BackgroundWorker pour votre opération asynchrone à l’aide du Concepteur WindowsForms.

Sous l’onglet Composants de la boîte à outils, faites glisser un BackgroundWorker vers le formulaire.

Ajouter des gestionnaires d’événements asynchrones

Vous êtes maintenant prêt à ajouter des gestionnaires d’événements pour les BackgroundWorker événements asynchrones du composant. L’opération fastidieuse qui s’exécutera en arrière-plan, qui calcule les nombres Fibonacci, est appelée par l’un de ces gestionnaires d’événements.

  1. Dans la fenêtre Propriétés , avec le BackgroundWorker composant toujours sélectionné, cliquez sur le bouton Événements . Double-cliquez sur les événements DoWork et RunWorkerCompleted pour créer des gestionnaires d'événements. Pour plus d’informations sur l’utilisation des gestionnaires d’événements, consultez Guide pratique pour créer des gestionnaires d’événements à l’aide du concepteur.

  2. Créez une méthode appelée ComputeFibonacci, dans votre formulaire. Cette méthode effectue le travail réel et s’exécute en arrière-plan. Ce code illustre l’implémentation récursive de l’algorithme Fibonacci, qui est particulièrement inefficace, prenant un temps exponentiel plus long pour un nombre plus grand. Il est utilisé ici à des fins d’illustration pour montrer une opération qui peut introduire de longs retards dans votre application.

    // 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. Dans le gestionnaire d’événements DoWork , ajoutez un appel à la ComputeFibonacci méthode. Prenez le premier paramètre pour ComputeFibonacci la Argument propriété du DoWorkEventArgs. Les paramètres BackgroundWorker et DoWorkEventArgs seront utilisés ultérieurement pour le compte rendu de l'avancement et la gestion de l'annulation. Affectez la valeur de retour de ComputeFibonacci à la propriété Result du DoWorkEventArgs. Ce résultat sera disponible pour le gestionnaire d’événements RunWorkerCompleted .

    Remarque

    Le DoWork gestionnaire d’événements ne référence pas directement la backgroundWorker1 variable d’instance, car cela couplerait ce gestionnaire d’événements à une instance spécifique de BackgroundWorker. Au lieu de cela, une référence à l’événement BackgroundWorker déclenché est récupérée à partir du sender paramètre. Cela est important lorsque le formulaire héberge plusieurs BackgroundWorker. Il est également important de ne pas manipuler d’objets d’interface utilisateur dans votre DoWork gestionnaire d’événements. Au lieu de cela, communiquez avec l’interface utilisateur via les BackgroundWorker événements.

    // 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. Dans le startAsyncButton gestionnaire d’événements du Click contrôle, ajoutez le code qui démarre l’opération asynchrone.

    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. Dans le RunWorkerCompleted gestionnaire d’événements, affectez le résultat du calcul au resultLabel contrôle.

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

Ajout de rapports de progression et prise en charge de l’annulation

Pour les opérations asynchrones qui prendront beaucoup de temps, il est souvent souhaitable de signaler la progression à l’utilisateur et de permettre à l’utilisateur d’annuler l’opération. La BackgroundWorker classe fournit un événement qui vous permet de publier la progression à mesure que votre opération en arrière-plan se poursuit. Il fournit également un indicateur qui permet à votre code de travail de détecter un appel à CancelAsync et de s'interrompre.

Implémenter des rapports de progression

  1. Dans la fenêtre Propriétés, sélectionnez backgroundWorker1. Définissez les propriétés WorkerReportsProgress et WorkerSupportsCancellation sur true.

  2. Déclarez deux variables dans le FibonacciCalculator formulaire. Celles-ci seront utilisées pour suivre la progression.

    int numberToCompute;
    int highestPercentageReached;
    
    private int numberToCompute = 0;
    private int highestPercentageReached = 0;
    
    Private numberToCompute As Integer = 0
    Private highestPercentageReached As Integer = 0
    
  3. Ajoutez un gestionnaire d’événements pour l’événement ProgressChanged . Dans le gestionnaire d’événements ProgressChanged, mettez à jour la propriété ProgressBar avec le paramètre ProgressPercentage.

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

Implémenter la prise en charge de l’annulation

  1. Dans le cancelAsyncButton gestionnaire d’événements du Click contrôle, ajoutez le code qui annule l’opération asynchrone.

    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. Les fragments de code suivants dans la méthode ComputeFibonacci rapportent la progression et prennent en charge l'annulation.

    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
    

Point de contrôle

À ce stade, vous pouvez compiler et exécuter l’application Fibonacci Calculator.

Appuyez sur F5 pour compiler et exécuter l’application.

Pendant que le calcul est en cours d’exécution en arrière-plan, vous verrez l’affichage ProgressBar de la progression du calcul vers la fin. Vous pouvez également annuler l’opération en attente.

Pour les petits nombres, le calcul doit être très rapide, mais pour les plus grands nombres, vous devriez voir un délai notable. Si vous entrez une valeur de 30 ou supérieure, vous devriez voir un délai de plusieurs secondes, en fonction de la vitesse de votre ordinateur. Pour les valeurs supérieures à 40, la fin du calcul peut prendre des minutes ou des heures. Alors que la calculatrice est occupée à calculer un grand nombre Fibonacci, notez que vous pouvez déplacer librement la forme autour, réduire, agrandir et même ignorer. Cela est dû au fait que le thread d’interface utilisateur principal n’attend pas la fin du calcul.

Étapes suivantes

Maintenant que vous avez implémenté un formulaire qui utilise un BackgroundWorker composant pour exécuter un calcul en arrière-plan, vous pouvez explorer d’autres possibilités pour les opérations asynchrones :

Voir aussi