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
Dans Visual Studio, créez un projet d’application Windows appelé
BackgroundWorkerExample
(Application>Windows Forms de nouveau projet>ou >Windows Forms).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
».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é sur91
.Ajoutez deux Button contrôles au formulaire.
Renommez le premier Button contrôle
startAsyncButton
et définissez la Text propriété surStart Async
. Renommez le deuxième Button contrôlecancelAsyncButton
et définissez la Text propriété surCancel Async
. Définissez sa Enabled propriété surfalse
.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.
Faites glisser un Label contrôle de la boîte à outils sur le formulaire et renommez-le
resultLabel
.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.
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.
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
Dans le gestionnaire d’événements DoWork , ajoutez un appel à la
ComputeFibonacci
méthode. Prenez le premier paramètre pourComputeFibonacci
la Argument propriété du DoWorkEventArgs. Les paramètres et BackgroundWorker les DoWorkEventArgs 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 deComputeFibonacci
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 dusender
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
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
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
Dans la fenêtre Propriétés, sélectionnez
backgroundWorker1
. Définissez les WorkerReportsProgress propriétés WorkerSupportsCancellationsurtrue
.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
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
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
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 :
Utilisez plusieurs BackgroundWorker objets pour plusieurs opérations simultanées.
Pour déboguer votre application multithread, consultez l’article Comment : utiliser la fenêtre Threads.
Implémentez votre propre composant qui prend en charge le modèle de programmation asynchrone. Pour plus d’informations, consultez l’article Vue d’ensemble du modèle asynchrone basé sur les événements.
Attention
Quand vous utilisez le multithreading, vous vous exposez potentiellement à des bogues très sérieux et complexes. Consultez les Meilleures pratiques pour le threading managé avant d’implémenter une solution qui utilise le multithreading.
Voir aussi
- System.ComponentModel.BackgroundWorker
- Threading managé
- Meilleures pratiques pour le threading managé
- Vue d’ensemble du modèle asynchrone basé sur des événements
- Procédure : implémenter un formulaire qui utilise une opération en arrière-plan
- Procédure pas à pas : exécution d’une opération en arrière-plan
- BackgroundWorker (composant)
.NET Desktop feedback