Procédure pas à pas : implémentation d'un formulaire qui utilise une opération d'arrière-plan
Si vous avez une opération dont l'exécution va prendre beaucoup de temps et que vous ne souhaitez pas que votre interface utilisateur cesse de répondre ou « se bloque », vous pouvez utiliser la classe BackgroundWorker pour exécuter l'opération sur un autre thread.
Cette procédure pas à pas illustre comment utiliser la classe BackgroundWorker pour exécuter « en arrière-plan » des calculs qui prennent du temps, pendant que l'interface utilisateur continue de répondre. Lorsque vous avez terminé, vous disposez alors d'une application qui calcule les nombres de Fibonacci de façon asynchrone. Si le calcul d'un nombre de Fibonacci élevé peut prendre un nombre important d'heures, le thread d'interface utilisateur principal n'est pas interrompu par ce délai, et le formulaire continu de répondre pendant le calcul.
Cette procédure pas à pas illustre les tâches suivantes :
Création d'une application Windows
Création d'un BackgroundWorker dans votre formulaire
Ajout de gestionnaires d'événements asynchrones
Ajout de rapport de progression et de prise en charge de l'annulation
Pour une liste complète du code utilisé dans cet exemple, consultez Comment : implémenter un formulaire qui utilise une opération d'arrière-plan.
Notes
Les boîtes de dialogue et les commandes de menu qui s'affichent peuvent être différentes de celles qui sont décrites dans l'aide, en fonction de vos paramètres actifs ou de l'édition utilisée. Pour modifier vos paramètres, choisissez Importation et exportation de paramètres dans le menu Outils. Pour plus d'informations, consultez Utilisation des paramètres.
Création du projet
La première étape consiste à créer le projet et à configurer le formulaire.
Pour créer un formulaire qui utilise une opération d'arrière-plan
Créez un projet d'application Windows appelé BackgroundWorkerExample. Pour plus d'informations, consultez Comment : créer un projet d'Application 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 à la question de savoir si vous souhaitez renommer toutes les références à l'élément de code "Form1".
Faites glisser un contrôle NumericUpDown de la Boîte à outils vers le formulaire. Attribuez à la propriété Minimum la valeur 1 à et la propriété Maximum la valeur 91.
Ajoutez deux contrôles Button au formulaire.
Renommez le premier contrôle Button en startAsyncButton et attribuez à la propriété Text la valeur Start Async. Renommez le second contrôle Button en cancelAsyncButton et attribuez à la propriété Text la valeur Cancel Async. affectez à sa propriété Enabled la valeur false.
Créez un gestionnaire d'événements pour les deux événements Click du contrôle Button. Pour plus d'informations, consultez Comment : créer des gestionnaires d'événements à l'aide du concepteur.
Faites glisser un contrôle Label de la Boîte à outils sur le formulaire et renommez-le resultLabel.
Faites glisser un contrôle ProgressBar de la Boîte à outils vers le formulaire.
Création d'un BackgroundWorker dans votre formulaire
Vous pouvez créer le BackgroundWorker pour votre opération asynchrone à l'aide du Concepteur Windows Forms.
Pour créer un BackgroundWorker avec le concepteur
- À partir de l'onglet Composants de la Boîte à outils, faites glisser un BackgroundWorker jusqu'au formulaire.
Ajout de gestionnaires d'événements asynchrones
Vous êtes maintenant prêt à ajouter des gestionnaires d'événements pour les événements asynchrones du composant BackgroundWorker. Cette longue opération qui s'exécute en arrière-plan, pour calculer les nombres de Fibonacci, est appelée par l'un de ces gestionnaires d'événements.
Pour implémenter des gestionnaires d'événements asynchrones
Dans la fenêtre Propriétés, laissez le composant BackgroundWorker sélectionné et 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 Comment : créer des gestionnaires d'événements à l'aide du concepteur.
Créez une nouvelle 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 montre que l'implémentation récursive de l'algorithme de Fibonacci, qui est notamment inefficace, prend un temps exponentiellement plus long pour les grands nombres. Il est utilisé ici à des fins d'illustration pour montrer une opération qui peut introduire de longs délais 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. Function ComputeFibonacci( _ ByVal n As Integer, _ ByVal worker As BackgroundWorker, _ ByVal e As DoWorkEventArgs) As Long ' The parameter n must be >= 0 and <= 91. ' Fib(n), with n > 91, overflows a long. If n < 0 OrElse n > 91 Then Throw New ArgumentException( _ "value must be >= 0 and <= 91", "n") End If Dim result As Long = 0 ' Abort the operation if the user has canceled. ' Note that a call to CancelAsync may have set ' CancellationPending to true just after the ' last invocation of this method exits, so this ' code will not have the opportunity to set the ' DoWorkEventArgs.Cancel flag to true. This means ' that RunWorkerCompletedEventArgs.Cancelled will ' not be set to true in your RunWorkerCompleted ' event handler. This is a race condition. If worker.CancellationPending Then e.Cancel = True Else If n < 2 Then result = 1 Else result = ComputeFibonacci(n - 1, worker, e) + _ ComputeFibonacci(n - 2, worker, e) End If ' Report progress as a percentage of the total task. Dim percentComplete As Integer = _ CSng(n) / CSng(numberToCompute) * 100 If percentComplete > highestPercentageReached Then highestPercentageReached = percentComplete worker.ReportProgress(percentComplete) End If End If Return result End Function
// This is the method that does the actual work. For this // example, it computes a Fibonacci number and // reports progress as it does its work. long ComputeFibonacci(int n, BackgroundWorker worker, DoWorkEventArgs e) { // The parameter n must be >= 0 and <= 91. // Fib(n), with n > 91, overflows a long. if ((n < 0) || (n > 91)) { throw new ArgumentException( "value must be >= 0 and <= 91", "n"); } long result = 0; // Abort the operation if the user has canceled. // Note that a call to CancelAsync may have set // CancellationPending to true just after the // last invocation of this method exits, so this // code will not have the opportunity to set the // DoWorkEventArgs.Cancel flag to true. This means // that RunWorkerCompletedEventArgs.Cancelled will // not be set to true in your RunWorkerCompleted // event handler. This is a race condition. if (worker.CancellationPending) { e.Cancel = true; } else { if (n < 2) { result = 1; } else { result = ComputeFibonacci(n - 1, worker, e) + ComputeFibonacci(n - 2, worker, e); } // Report progress as a percentage of the total task. int percentComplete = (int)((float)n / (float)numberToCompute * 100); if (percentComplete > highestPercentageReached) { highestPercentageReached = percentComplete; worker.ReportProgress(percentComplete); } } return result; }
// This is the method that does the actual work. For this // example, it computes a Fibonacci number and // reports progress as it does its work. long ComputeFibonacci( int n, BackgroundWorker^ worker, DoWorkEventArgs ^ e ) { // The parameter n must be >= 0 and <= 91. // Fib(n), with n > 91, overflows a long. if ( (n < 0) || (n > 91) ) { throw gcnew ArgumentException( "value must be >= 0 and <= 91","n" ); } long result = 0; // Abort the operation if the user has cancelled. // Note that a call to CancelAsync may have set // CancellationPending to true just after the // last invocation of this method exits, so this // code will not have the opportunity to set the // DoWorkEventArgs.Cancel flag to true. This means // that RunWorkerCompletedEventArgs.Cancelled will // not be set to true in your RunWorkerCompleted // event handler. This is a race condition. if ( worker->CancellationPending ) { e->Cancel = true; } else { if ( n < 2 ) { result = 1; } else { result = ComputeFibonacci( n - 1, worker, e ) + ComputeFibonacci( n - 2, worker, e ); } // Report progress as a percentage of the total task. int percentComplete = (int)((float)n / (float)numberToCompute * 100); if ( percentComplete > highestPercentageReached ) { highestPercentageReached = percentComplete; worker->ReportProgress( percentComplete ); } } return result; }
Dans le gestionnaire d'événements DoWork, ajoutez un appel à la méthode ComputeFibonacci. Prenez le premier paramètre pour ComputeFibonacci à partir de la propriété Argument du DoWorkEventArgs. Les paramètres BackgroundWorker et DoWorkEventArgs seront utilisés ultérieurement pour le rapport de progression et la prise en charge de l'annulation. Assignez la valeur de retour de ComputeFibonacci à la propriété Result du DoWorkEventArgs. Ce résultat sera disponible au gestionnaire d'événements RunWorkerCompleted.
Notes
Le gestionnaire d'événements DoWork ne fait pas directement référence à la variable d'instance backgroundWorker1, car cela associerait ce gestionnaire d'événements à une instance spécifique de BackgroundWorker. À la place, une référence au BackgroundWorker qui a déclenché cet événement est récupérée à partir du paramètre sender. Ceci est important lorsque le formulaire héberge plusieurs BackgroundWorker. Il est également important de ne pas manipuler d'objets interface utilisateur dans votre gestionnaire d'événements DoWork. À la place, communiquez à l'interface utilisateur via les événements BackgroundWorker.
' This event handler is where the actual work is done. Private Sub backgroundWorker1_DoWork( _ ByVal sender As Object, _ ByVal e As DoWorkEventArgs) _ Handles backgroundWorker1.DoWork ' Get the BackgroundWorker object that raised this event. Dim worker As BackgroundWorker = _ CType(sender, BackgroundWorker) ' Assign the result of the computation ' to the Result property of the DoWorkEventArgs ' object. This is will be available to the ' RunWorkerCompleted eventhandler. e.Result = ComputeFibonacci(e.Argument, worker, e) End Sub 'backgroundWorker1_DoWork
// This event handler is where the actual, // potentially time-consuming work is done. private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { // Get the BackgroundWorker that raised this event. BackgroundWorker worker = sender as BackgroundWorker; // Assign the result of the computation // to the Result property of the DoWorkEventArgs // object. This is will be available to the // RunWorkerCompleted eventhandler. e.Result = ComputeFibonacci((int)e.Argument, worker, e); }
// This event handler is where the actual, // potentially time-consuming work is done. void backgroundWorker1_DoWork( Object^ sender, DoWorkEventArgs^ e ) { // Get the BackgroundWorker that raised this event. BackgroundWorker^ worker = dynamic_cast<BackgroundWorker^>(sender); // Assign the result of the computation // to the Result property of the DoWorkEventArgs // object. This is will be available to the // RunWorkerCompleted eventhandler. e->Result = ComputeFibonacci( safe_cast<Int32>(e->Argument), worker, e ); }
Dans le gestionnaire d'événement Click du contrôle startAsyncButton, ajoutez du code pour lancer l'opération asynchrone.
Private Sub startAsyncButton_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles startAsyncButton.Click ' Reset the text in the result label. resultLabel.Text = [String].Empty ' Disable the UpDown control until ' the asynchronous operation is done. Me.numericUpDown1.Enabled = False ' Disable the Start button until ' the asynchronous operation is done. Me.startAsyncButton.Enabled = False ' Enable the Cancel button while ' the asynchronous operation runs. Me.cancelAsyncButton.Enabled = True ' Get the value from the UpDown control. numberToCompute = CInt(numericUpDown1.Value) ' Reset the variable for percentage tracking. highestPercentageReached = 0 ' Start the asynchronous operation. backgroundWorker1.RunWorkerAsync(numberToCompute) End Sub
private void startAsyncButton_Click(System.Object sender, System.EventArgs e) { // Reset the text in the result label. resultLabel.Text = String.Empty; // Disable the UpDown control until // the asynchronous operation is done. this.numericUpDown1.Enabled = false; // Disable the Start button until // the asynchronous operation is done. this.startAsyncButton.Enabled = false; // Enable the Cancel button while // the asynchronous operation runs. this.cancelAsyncButton.Enabled = true; // Get the value from the UpDown control. numberToCompute = (int)numericUpDown1.Value; // Reset the variable for percentage tracking. highestPercentageReached = 0; // Start the asynchronous operation. backgroundWorker1.RunWorkerAsync(numberToCompute); }
void startAsyncButton_Click( System::Object^ /*sender*/, System::EventArgs^ /*e*/ ) { // Reset the text in the result label. resultLabel->Text = String::Empty; // Disable the UpDown control until // the asynchronous operation is done. this->numericUpDown1->Enabled = false; // Disable the Start button until // the asynchronous operation is done. this->startAsyncButton->Enabled = false; // Enable the Cancel button while // the asynchronous operation runs. this->cancelAsyncButton->Enabled = true; // Get the value from the UpDown control. numberToCompute = (int)numericUpDown1->Value; // Reset the variable for percentage tracking. highestPercentageReached = 0; // Start the asynchronous operation. backgroundWorker1->RunWorkerAsync( numberToCompute ); }
Dans le gestionnaire d'événements RunWorkerCompleted, assignez le résultat du calcul au contrôle resultLabel.
' This event handler deals with the results of the ' background operation. Private Sub backgroundWorker1_RunWorkerCompleted( _ ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) _ Handles backgroundWorker1.RunWorkerCompleted ' First, handle the case where an exception was thrown. If (e.Error IsNot Nothing) Then MessageBox.Show(e.Error.Message) ElseIf e.Cancelled Then ' Next, handle the case where the user canceled the ' operation. ' Note that due to a race condition in ' the DoWork event handler, the Cancelled ' flag may not have been set, even though ' CancelAsync was called. resultLabel.Text = "Canceled" Else ' Finally, handle the case where the operation succeeded. resultLabel.Text = e.Result.ToString() End If ' Enable the UpDown control. Me.numericUpDown1.Enabled = True ' Enable the Start button. startAsyncButton.Enabled = True ' Disable the Cancel button. cancelAsyncButton.Enabled = False End Sub 'backgroundWorker1_RunWorkerCompleted
// This event handler deals with the results of the // background operation. private void backgroundWorker1_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e) { // First, handle the case where an exception was thrown. if (e.Error != null) { MessageBox.Show(e.Error.Message); } else if (e.Cancelled) { // Next, handle the case where the user canceled // the operation. // Note that due to a race condition in // the DoWork event handler, the Cancelled // flag may not have been set, even though // CancelAsync was called. resultLabel.Text = "Canceled"; } else { // Finally, handle the case where the operation // succeeded. resultLabel.Text = e.Result.ToString(); } // Enable the UpDown control. this.numericUpDown1.Enabled = true; // Enable the Start button. startAsyncButton.Enabled = true; // Disable the Cancel button. cancelAsyncButton.Enabled = false; }
// This event handler deals with the results of the // background operation. void backgroundWorker1_RunWorkerCompleted( Object^ /*sender*/, RunWorkerCompletedEventArgs^ e ) { // First, handle the case where an exception was thrown. if ( e->Error != nullptr ) { MessageBox::Show( e->Error->Message ); } else if ( e->Cancelled ) { // Next, handle the case where the user cancelled // the operation. // Note that due to a race condition in // the DoWork event handler, the Cancelled // flag may not have been set, even though // CancelAsync was called. resultLabel->Text = "Cancelled"; } else { // Finally, handle the case where the operation // succeeded. resultLabel->Text = e->Result->ToString(); } // Enable the UpDown control. this->numericUpDown1->Enabled = true; // Enable the Start button. startAsyncButton->Enabled = true; // Disable the Cancel button. cancelAsyncButton->Enabled = false; }
Ajout de rapport de progression et de prise en charge de l'annulation
Pour les opérations asynchrones qui prennent un certain temps, il est souvent souhaitable de signaler la progression à l'utilisateur et de permettre à celui-ci d'annuler l'opération. La classe BackgroundWorker fournit un événement qui vous permet de publier la progression comme résultat de l'opération d'arrière-plan. Il fournit également un indicateur qui permet à votre code de travail de détecter un appel à CancelAsync et l'interrompre.
Pour implémenter un rapport de progression
Dans la fenêtre Propriétés, sélectionnez la propriété backgroundWorker1. Attribuez aux propriétés WorkerReportsProgress et WorkerSupportsCancellation la valeur true.
Déclarez deux variables dans le formulaire FibonacciCalculator. Celles-ci seront utilisées pour suivre la progression.
Private numberToCompute As Integer = 0 Private highestPercentageReached As Integer = 0
private int numberToCompute = 0; private int highestPercentageReached = 0;
int numberToCompute; int highestPercentageReached;
Ajoute un gestionnaire d'événements pour l'événement ProgressChanged. Dans le gestionnaire d'événements ProgressChanged, mettez à jour le ProgressBar avec la propriété ProgressPercentage du paramètre ProgressChangedEventArgs.
' This event handler updates the progress bar. Private Sub backgroundWorker1_ProgressChanged( _ ByVal sender As Object, ByVal e As ProgressChangedEventArgs) _ Handles backgroundWorker1.ProgressChanged Me.progressBar1.Value = e.ProgressPercentage End Sub
// This event handler updates the progress bar. private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.progressBar1.Value = e.ProgressPercentage; }
// This event handler updates the progress bar. void backgroundWorker1_ProgressChanged( Object^ /*sender*/, ProgressChangedEventArgs^ e ) { this->progressBar1->Value = e->ProgressPercentage; }
Pour implémenter la prise en charge de l'annulation
Dans le gestionnaire d'événements du contrôle cancelAsyncButton Click, ajoutez le code qui annule l'opération asynchrone.
Private Sub cancelAsyncButton_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles cancelAsyncButton.Click ' Cancel the asynchronous operation. Me.backgroundWorker1.CancelAsync() ' Disable the Cancel button. cancelAsyncButton.Enabled = False End Sub 'cancelAsyncButton_Click
private void cancelAsyncButton_Click(System.Object sender, System.EventArgs e) { // Cancel the asynchronous operation. this.backgroundWorker1.CancelAsync(); // Disable the Cancel button. cancelAsyncButton.Enabled = false; }
void cancelAsyncButton_Click( System::Object^ /*sender*/, System::EventArgs^ /*e*/ ) { // Cancel the asynchronous operation. this->backgroundWorker1->CancelAsync(); // Disable the Cancel button. cancelAsyncButton->Enabled = false; }
Les fragments de code suivants dans le rapport de méthode ComputeFibonacci signalent la progression et prennent en charge l'annulation.
If worker.CancellationPending Then e.Cancel = True ... ' Report progress as a percentage of the total task. Dim percentComplete As Integer = _ CSng(n) / CSng(numberToCompute) * 100 If percentComplete > highestPercentageReached Then highestPercentageReached = percentComplete worker.ReportProgress(percentComplete) End If
if (worker.CancellationPending) { e.Cancel = true; } ... // Report progress as a percentage of the total task. int percentComplete = (int)((float)n / (float)numberToCompute * 100); if (percentComplete > highestPercentageReached) { highestPercentageReached = percentComplete; worker.ReportProgress(percentComplete); }
if ( worker->CancellationPending ) { e->Cancel = true; } ... // Report progress as a percentage of the total task. int percentComplete = (int)((float)n / (float)numberToCompute * 100); if ( percentComplete > highestPercentageReached ) { highestPercentageReached = percentComplete; worker->ReportProgress( percentComplete ); }
Point de contrôle
À ce stade, vous pouvez compiler et exécuter l'application Fibonacci Calculator.
Pour tester le projet
Appuyez sur F5 pour compiler et exécuter l'application.
Pendant que le calcul s'exécute en arrière-plan, vous pouvez consulter ProgressBar qui affiche la progression du calcul en voie d'achèvement. Vous pouvez également annuler l'opération en suspens.
Pour les petits nombres, le calcul doit être très rapide, mais pour les grands nombres, vous devez constater un délai notable. Si vous entrez une valeur égale ou supérieure à 30, vous devez constater un délai de plusieurs secondes, selon la vitesse de votre ordinateur. Pour les valeurs supérieures à 40, le calcul peut durer plusieurs minutes, voire des heures. Pendant que la calculatrice est occupée à calculer un grand nombre de Fibonacci, notez que vous pouvez déplacer librement le formulaire, le réduire, l'agrandir et même le faire disparaître. L'explication en est simple : le thread d'interface utilisateur principal n'attend pas que le calcul se termine.
Étapes suivantes
Maintenant que vous avez implémenté un formulaire qui utilise un composant BackgroundWorker pour exécuter un calcul en arrière-plan, vous pouvez explorer d'autres possibilités pour les opérations asynchrones :
Utilisez plusieurs objets BackgroundWorker pour plusieurs opérations simultanées.
Pour déboguer votre application multithread, consultez 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 Vue d'ensemble du modèle asynchrone basé sur des événements.
Avertissement
Lorsque vous utilisez le multithreading, vous vous exposez potentiellement à des bogues très sérieux et complexes. Consultez Meilleures pratiques pour le threading managé avant d'implémenter une solution utilisant le multithreading.
Voir aussi
Tâches
Comment : implémenter un formulaire qui utilise une opération d'arrière-plan
Procédure pas à pas : exécution d'une opération en arrière-plan
Référence
Concepts
Meilleures pratiques pour le threading managé
Autres ressources
Multithreading dans les composants