Procédure pas à pas : implémentation d'un formulaire qui utilise une opération d'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 avez terminé, vous disposerez d’une application qui calcule les nombres de Fibonacci de manière asynchrone. Même si le calcul d’un nombre de Fibonacci élevé peut prendre beaucoup de temps, le thread d’interface utilisateur principal ne sera pas interrompu et le formulaire restera réactif tout au long de l’opération de calcul.

Cette procédure pas à pas décrit notamment les tâches suivantes :

  • Création d’une application Windows

  • Création d’un BackgroundWorker 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 l’article Comment : implémenter un formulaire qui utilise une opération d’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 (Application>>Windows Forms de nouveau projet>ou Visual Basic>Classic Desktop>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 l’on vous demande si vous souhaitez 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 Button contrôles 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 Enabled propriété sur false.

  6. Créez un gestionnaire d’événements pour les deux Button événements des Click contrôles. Pour plus d’informations, consultez l’article Comment : 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 l’opération BackgroundWorker 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 de longue durée qui s’exécutera en arrière-plan et calculera les nombres de 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 DoWork événements et RunWorkerCompleted les événements pour créer des gestionnaires d’événements. Pour plus d’informations sur l’utilisation des gestionnaires d’événements, consultez l’article Comment : créer des gestionnaires d’événements à l’aide du concepteur.

  2. Dans votre formulaire, créez une nouvelle méthode appelée ComputeFibonacci. Cette méthode exécute l’opération requise en arrière-plan. Ce code décrit l’implémentation récursive de l’algorithme de Fibonacci qui est particulièrement inefficace et nécessite beaucoup plus de temps pour traiter des nombres élevés. Il est utilisé ici à titre d’exemple, dans le but d’illustrer une opération qui peut occasionner des temps d’inactivité assez longs pour 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 et DoWorkEventArgs les BackgroundWorker rapports d’avancement seront utilisés ultérieurement pour la prise en charge des rapports d’avancement et de l’annulation. Affectez la valeur de retour à partir de ComputeFibonacci la Result propriété 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 chronophages, il est souvent utile d’informer l’utilisateur de la progression et de lui permettre 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 et CancelAsync d’interrompre lui-même.

Implémenter des rapports de progression

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

  2. Déclarez deux variables dans le formulaire FibonacciCalculator. Elles 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 ProgressChanged gestionnaire d’événements, mettez à jour la ProgressBarProgressPercentage propriété du ProgressChangedEventArgs paramètre.

    // 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 de la méthode ComputeFibonacci indiquent la progression et prennent en charge l’opération d’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 nombres peu élevés, le calcul devrait être très rapide, mais pour les nombres plus élevés, le délai est bien plus long. Si vous entrez une valeur supérieure ou égale à 30, vous devriez constater un délai de quelques secondes, selon la vitesse de votre ordinateur. Pour des valeurs supérieures à 40, l’opération de calcul peut prendre quelques minutes ou heures. Tandis que la calculatrice traite un nombre de Fibonacci élevé, vous pouvez librement déplacer le formulaire, le réduire, l’agrandir et même le faire disparaître. Cela est possible car le thread d’interface utilisateur principal n’attend pas que le calcul soit terminé.

É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