Procédure : implémenter un composant qui prend en charge le modèle asynchrone basé sur des événements
Si vous écrivez une classe qui comporte certaines opérations pouvant entraîner d’importants ralentissements, pensez à lui affecter des fonctionnalités asynchrones en implémentant la Vue d’ensemble du modèle asynchrone basé sur les événements.
Cette procédure pas à pas montre comment créer un composant qui applique le modèle asynchrone basé sur les événements. Il est implémenté à l’aide de classes d’assistance provenant de l’espace de noms System.ComponentModel, ce qui garantit son bon fonctionnement quel que soit le modèle d’application, notamment ASP.NET, les applications console et les applications Windows Forms. Vous pouvez également le concevoir avec un contrôle PropertyGrid et vos propres concepteurs personnalisés.
À la fin, vous disposerez d’une application qui calcule les nombres premiers de manière asynchrone. Votre application aura un thread d’interface utilisateur (IU) principal et un thread pour chaque calcul de nombres premiers. Bien qu’il puisse être long de tester si un grand nombre est un nombre premier, le thread d’UI principal ne sera pas interrompu par ce ralentissement, et le formulaire restera réactif tout au long des calculs. Vous pourrez lancer autant de calculs que vous le souhaiterez simultanément, et annuler de manière sélective des calculs en attente.
Cette procédure pas à pas décrit notamment les tâches suivantes :
Création du composant
Définir des délégués et des événements asynchrones publics
Définir des délégués privés
Implémenter des événements publics
Implémenter la méthode d’achèvement
Implémenter les méthodes de travail
Implémenter des méthodes de démarrage et d’annulation
Pour copier le code dans cette rubrique sous la forme d’une liste unique, consultez Comment : implémenter un client du modèle asynchrone basé sur les événements.
Création du composant
La première étape consiste à créer le composant qui implémentera le modèle asynchrone basé sur les événements.
Pour créer le composant :
- Créez une classe nommée
PrimeNumberCalculator
qui hérite de Component.
Définir des délégués et des événements asynchrones publics
Votre composant communique avec les clients à l’aide d’événements. L’événement MethodNameCompleted avertit les clients de l’achèvement d’une tâche asynchrone, et l’événement MethodNameProgressChanged les informe de la progression d’une tâche asynchrone.
Pour définir des événements asynchrones pour les clients de votre composant :
Importer les espaces de noms System.Threading et System.Collections.Specialized en haut de votre fichier.
using System; using System.Collections; using System.Collections.Specialized; using System.ComponentModel; using System.Data; using System.Drawing; using System.Globalization; using System.Threading; using System.Windows.Forms;
Imports System.Collections Imports System.Collections.Specialized Imports System.ComponentModel Imports System.Drawing Imports System.Globalization Imports System.Threading Imports System.Windows.Forms
Avant la définition de la classe
PrimeNumberCalculator
, déclarez des délégués pour les événements de progression et d’achèvement.public delegate void ProgressChangedEventHandler( ProgressChangedEventArgs e); public delegate void CalculatePrimeCompletedEventHandler( object sender, CalculatePrimeCompletedEventArgs e);
Public Delegate Sub ProgressChangedEventHandler( _ ByVal e As ProgressChangedEventArgs) Public Delegate Sub CalculatePrimeCompletedEventHandler( _ ByVal sender As Object, _ ByVal e As CalculatePrimeCompletedEventArgs)
Dans la définition de la classe
PrimeNumberCalculator
, déclarez des événements signalant la progression et l’achèvement aux clients.public event ProgressChangedEventHandler ProgressChanged; public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
Public Event ProgressChanged _ As ProgressChangedEventHandler Public Event CalculatePrimeCompleted _ As CalculatePrimeCompletedEventHandler
Après la définition de la classe
PrimeNumberCalculator
, dérivez la classeCalculatePrimeCompletedEventArgs
pour signaler le résultat de chaque calcul au gestionnaire d’événements du client pour l’événementCalculatePrimeCompleted
. Outre les propriétésAsyncCompletedEventArgs
, cette classe permet au client déterminer quel nombre a été testé, s’il est premier et, si ce n’est pas le cas, quel est le premier diviseur.public class CalculatePrimeCompletedEventArgs : AsyncCompletedEventArgs { private int numberToTestValue = 0; private int firstDivisorValue = 1; private bool isPrimeValue; public CalculatePrimeCompletedEventArgs( int numberToTest, int firstDivisor, bool isPrime, Exception e, bool canceled, object state) : base(e, canceled, state) { this.numberToTestValue = numberToTest; this.firstDivisorValue = firstDivisor; this.isPrimeValue = isPrime; } public int NumberToTest { get { // Raise an exception if the operation failed or // was canceled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return numberToTestValue; } } public int FirstDivisor { get { // Raise an exception if the operation failed or // was canceled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return firstDivisorValue; } } public bool IsPrime { get { // Raise an exception if the operation failed or // was canceled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return isPrimeValue; } } }
Public Class CalculatePrimeCompletedEventArgs Inherits AsyncCompletedEventArgs Private numberToTestValue As Integer = 0 Private firstDivisorValue As Integer = 1 Private isPrimeValue As Boolean Public Sub New( _ ByVal numberToTest As Integer, _ ByVal firstDivisor As Integer, _ ByVal isPrime As Boolean, _ ByVal e As Exception, _ ByVal canceled As Boolean, _ ByVal state As Object) MyBase.New(e, canceled, state) Me.numberToTestValue = numberToTest Me.firstDivisorValue = firstDivisor Me.isPrimeValue = isPrime End Sub Public ReadOnly Property NumberToTest() As Integer Get ' Raise an exception if the operation failed ' or was canceled. RaiseExceptionIfNecessary() ' If the operation was successful, return ' the property value. Return numberToTestValue End Get End Property Public ReadOnly Property FirstDivisor() As Integer Get ' Raise an exception if the operation failed ' or was canceled. RaiseExceptionIfNecessary() ' If the operation was successful, return ' the property value. Return firstDivisorValue End Get End Property Public ReadOnly Property IsPrime() As Boolean Get ' Raise an exception if the operation failed ' or was canceled. RaiseExceptionIfNecessary() ' If the operation was successful, return ' the property value. Return isPrimeValue End Get End Property End Class
Point de contrôle 1
Vous pouvez maintenant générer le composant.
Pour tester le composant :
Compilez le composant.
Vous recevrez deux avertissements du compilateur :
warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.ProgressChanged' is never used warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.CalculatePrimeCompleted' is never used
Ces avertissements seront effacés dans la section suivante.
Définir des délégués privés
Les aspects asynchrones du composant PrimeNumberCalculator
sont implémentés en interne avec un délégué spécial appelé SendOrPostCallback. SendOrPostCallback représente une méthode de rappel qui s’exécute sur un thread ThreadPool. Elle doit avoir une signature qui prend un seul paramètre de type Object, ce qui signifie qu’il vous faudra transmettre l’état entre les délégués dans une classe wrapper. Pour plus d’informations, consultez SendOrPostCallback.
Pour implémenter le comportement asynchrone interne du composant :
Déclarez et créez le délégué SendOrPostCallback dans la classe
PrimeNumberCalculator
. Créez les objets SendOrPostCallback dans une méthode utilitaire nomméeInitializeDelegates
.Vous aurez besoin de deux délégués : un pour signaler la progression au client, l’autre pour lui signaler l’achèvement.
private SendOrPostCallback onProgressReportDelegate; private SendOrPostCallback onCompletedDelegate;
Private onProgressReportDelegate As SendOrPostCallback Private onCompletedDelegate As SendOrPostCallback
protected virtual void InitializeDelegates() { onProgressReportDelegate = new SendOrPostCallback(ReportProgress); onCompletedDelegate = new SendOrPostCallback(CalculateCompleted); }
Protected Overridable Sub InitializeDelegates() onProgressReportDelegate = _ New SendOrPostCallback(AddressOf ReportProgress) onCompletedDelegate = _ New SendOrPostCallback(AddressOf CalculateCompleted) End Sub
Appelez la méthode
InitializeDelegates
dans le constructeur de votre composant.public PrimeNumberCalculator() { InitializeComponent(); InitializeDelegates(); }
Public Sub New() InitializeComponent() InitializeDelegates() End Sub
Déclarez un délégué dans la classe
PrimeNumberCalculator
qui gère concrètement le travail à faire de façon asynchrone. Il inclut dans un wrapper la méthode de travail qui teste si un nombre est premier. Le délégué prend un paramètre AsyncOperation, qui sera utilisé pour effectuer le suivi de la durée de vie de l’opération asynchrone.private delegate void WorkerEventHandler( int numberToCheck, AsyncOperation asyncOp);
Private Delegate Sub WorkerEventHandler( _ ByVal numberToCheck As Integer, _ ByVal asyncOp As AsyncOperation)
Créez une collection pour gérer la durée de vie des opérations asynchrones en attente. Le client a besoin d’un moyen d’effectuer le suivi des opérations à l’exécution et à l’achèvement, en transmettant un ID de tâche ou un jeton unique lorsqu’il effectue l’appel à la méthode asynchrone. Le composant
PrimeNumberCalculator
doit garder la trace de chaque appel en associant l’ID de tâche à l’appel correspondant. Si le client transmet un ID de tâche non unique, le composantPrimeNumberCalculator
doit lever une exception.Le composant
PrimeNumberCalculator
garde la trace de l’ID de tâche à l’aide d’une classe de collection spéciale appelée HybridDictionary. Dans la définition de classe, créez un HybridDictionary nomméuserStateToLifetime
.private HybridDictionary userStateToLifetime = new HybridDictionary();
Private userStateToLifetime As New HybridDictionary()
Implémenter des événements publics
Les composants qui implémentent le modèle asynchrone basé sur les événements communiquent avec les clients à l’aide d’événements. Ces événements sont appelés sur le thread adéquat à l’aide de la classe AsyncOperation.
Pour déclencher des événements sur les clients du composant :
Implémentez des événements publics permettant d’informer les clients. Vous aurez besoin d’un événement pour signaler la progression et d’un autre pour signaler l’achèvement.
// This method is invoked via the AsyncOperation object, // so it is guaranteed to be executed on the correct thread. private void CalculateCompleted(object operationState) { CalculatePrimeCompletedEventArgs e = operationState as CalculatePrimeCompletedEventArgs; OnCalculatePrimeCompleted(e); } // This method is invoked via the AsyncOperation object, // so it is guaranteed to be executed on the correct thread. private void ReportProgress(object state) { ProgressChangedEventArgs e = state as ProgressChangedEventArgs; OnProgressChanged(e); } protected void OnCalculatePrimeCompleted( CalculatePrimeCompletedEventArgs e) { if (CalculatePrimeCompleted != null) { CalculatePrimeCompleted(this, e); } } protected void OnProgressChanged(ProgressChangedEventArgs e) { if (ProgressChanged != null) { ProgressChanged(e); } }
' This method is invoked via the AsyncOperation object, ' so it is guaranteed to be executed on the correct thread. Private Sub CalculateCompleted(ByVal operationState As Object) Dim e As CalculatePrimeCompletedEventArgs = operationState OnCalculatePrimeCompleted(e) End Sub ' This method is invoked via the AsyncOperation object, ' so it is guaranteed to be executed on the correct thread. Private Sub ReportProgress(ByVal state As Object) Dim e As ProgressChangedEventArgs = state OnProgressChanged(e) End Sub Protected Sub OnCalculatePrimeCompleted( _ ByVal e As CalculatePrimeCompletedEventArgs) RaiseEvent CalculatePrimeCompleted(Me, e) End Sub Protected Sub OnProgressChanged( _ ByVal e As ProgressChangedEventArgs) RaiseEvent ProgressChanged(e) End Sub
Implémenter la méthode d’achèvement
Le délégué d’achèvement est la méthode qui appelle le comportement asynchrone à threads libres sous-jacent lorsque l’opération asynchrone aboutit correctement, se termine par une erreur ou est annulée. Cet appel a lieu sur un thread arbitraire.
C’est dans cette méthode que l’ID de tâche du client est supprimé de la collection interne de jetons de clients uniques. Cette méthode met également fin à la durée de vie d’une opération asynchrone en particulier en appelant la méthode PostOperationCompleted sur la AsyncOperation correspondante. Cet appel déclenche l’événement d’achèvement sur le thread adapté au modèle d’application. Une fois que la méthode PostOperationCompleted a été appelée, cette instance de AsyncOperation n’est plus utilisable, et toute tentative ultérieure de l’utiliser lèvera une exception.
La signature CompletionMethod
doit contenir tout l’état nécessaire pour décrire le résultat de l’opération asynchrone. Elle conserve l’état du nombre qui a été testé par cette opération asynchrone en particulier, l’information consistant à savoir si le nombre est premier ou non et la valeur de son premier diviseur si ce n’est pas le cas. Elle renferme également l’état décrivant les exceptions qui sont produites, et la AsyncOperation correspondant à cette tâche particulière.
Pour achever une opération asynchrone :
Implémentez la méthode d’achèvement. Elle prend six paramètres, qu’elle utilise pour remplir un
CalculatePrimeCompletedEventArgs
qui est retourné au client par le biais duCalculatePrimeCompletedEventHandler
du client. Elle supprime le jeton d’ID tâche du client de la collection interne et met fin à la durée de vie de l’opération asynchrone par un appel à PostOperationCompleted. La AsyncOperation marshale l’appel au thread ou au contexte adapté au modèle d'application.// This is the method that the underlying, free-threaded // asynchronous behavior will invoke. This will happen on // an arbitrary thread. private void CompletionMethod( int numberToTest, int firstDivisor, bool isPrime, Exception exception, bool canceled, AsyncOperation asyncOp ) { // If the task was not previously canceled, // remove the task from the lifetime collection. if (!canceled) { lock (userStateToLifetime.SyncRoot) { userStateToLifetime.Remove(asyncOp.UserSuppliedState); } } // Package the results of the operation in a // CalculatePrimeCompletedEventArgs. CalculatePrimeCompletedEventArgs e = new CalculatePrimeCompletedEventArgs( numberToTest, firstDivisor, isPrime, exception, canceled, asyncOp.UserSuppliedState); // End the task. The asyncOp object is responsible // for marshaling the call. asyncOp.PostOperationCompleted(onCompletedDelegate, e); // Note that after the call to OperationCompleted, // asyncOp is no longer usable, and any attempt to use it // will cause an exception to be thrown. }
' This is the method that the underlying, free-threaded ' asynchronous behavior will invoke. This will happen on ' an arbitrary thread. Private Sub CompletionMethod( _ ByVal numberToTest As Integer, _ ByVal firstDivisor As Integer, _ ByVal prime As Boolean, _ ByVal exc As Exception, _ ByVal canceled As Boolean, _ ByVal asyncOp As AsyncOperation) ' If the task was not previously canceled, ' remove the task from the lifetime collection. If Not canceled Then SyncLock userStateToLifetime.SyncRoot userStateToLifetime.Remove(asyncOp.UserSuppliedState) End SyncLock End If ' Package the results of the operation in a ' CalculatePrimeCompletedEventArgs. Dim e As New CalculatePrimeCompletedEventArgs( _ numberToTest, _ firstDivisor, _ prime, _ exc, _ canceled, _ asyncOp.UserSuppliedState) ' End the task. The asyncOp object is responsible ' for marshaling the call. asyncOp.PostOperationCompleted(onCompletedDelegate, e) ' Note that after the call to PostOperationCompleted, asyncOp ' is no longer usable, and any attempt to use it will cause. ' an exception to be thrown. End Sub
Point de contrôle 2
Vous pouvez maintenant générer le composant.
Pour tester le composant :
Compilez le composant.
Vous recevrez un avertissement du compilateur :
warning CS0169: The private field 'AsynchronousPatternExample.PrimeNumberCalculator.workerDelegate' is never used
Cet avertissement sera résolu dans la section suivante.
Implémenter les méthodes de travail
Pour le moment, vous avez implémenté le code asynchrone d’accompagnement du composant PrimeNumberCalculator
. Vous pouvez maintenant écrire le code qui effectue concrètement le travail. Vous allez implémenter trois méthodes : CalculateWorker
, BuildPrimeNumberList
et IsPrime
. Ensemble, BuildPrimeNumberList
et IsPrime
composent un algorithme connu, appelé le crible d'Ératosthène, qui détermine si un nombre est premier en recherchant tous les nombres premiers jusqu'à la racine carrée du nombre testé. Si aucun diviseur n’est trouvé jusque-là, le numéro testé est un nombre premier.
Si ce composant était écrit dans une optique d’efficacité maximale, il mémoriserait tous les nombres premiers découverts par différents appels sur différents nombres testés. Il vérifierait également les diviseurs triviaux, comme 2, 3 et 5. Cependant, l’objectif de cet exemple est de montrer comment exécuter des opérations longues de façon asynchrone ; nous vous laissons donc le soin d’implémenter ces optimisations pour vous exercer.
La méthode CalculateWorker
est incluse dans un délégué et invoquée de façon asynchrone avec un appel à BeginInvoke
.
Notes
Le signalement de la progression est implémenté dans la méthode BuildPrimeNumberList
. Sur les ordinateurs rapides, il est possible de déclencher les événements ProgressChanged
à courts intervalles. Le thread du client, sur lequel ces événements sont déclenchés, doit être en mesure de gérer cette situation. Le code de l’interface utilisateur pourrait être submergé par les messages et incapable de suivre le rythme, ce qui provoquerait une absence de réponse. Vous trouverez un exemple de client qui gère cette situation sur la page Guide pratique : implémenter un client du modèle asynchrone basé sur les événements.
Pour exécuter de façon asynchrone le calcul de nombres premiers :
Implémentez la méthode utilitaire
TaskCanceled
. Elle recherche la collection de durée de vie de la tâche pour l’ID de tâche donné, et retournetrue
si l’ID de tâche est introuvable.// Utility method for determining if a // task has been canceled. private bool TaskCanceled(object taskId) { return( userStateToLifetime[taskId] == null ); }
' Utility method for determining if a ' task has been canceled. Private Function TaskCanceled(ByVal taskId As Object) As Boolean Return (userStateToLifetime(taskId) Is Nothing) End Function
Implémentez la méthode
CalculateWorker
. Elle prend deux paramètres : un nombre à tester et une AsyncOperation.// This method performs the actual prime number computation. // It is executed on the worker thread. private void CalculateWorker( int numberToTest, AsyncOperation asyncOp) { bool isPrime = false; int firstDivisor = 1; Exception e = null; // Check that the task is still active. // The operation may have been canceled before // the thread was scheduled. if (!TaskCanceled(asyncOp.UserSuppliedState)) { try { // Find all the prime numbers up to // the square root of numberToTest. ArrayList primes = BuildPrimeNumberList( numberToTest, asyncOp); // Now we have a list of primes less than // numberToTest. isPrime = IsPrime( primes, numberToTest, out firstDivisor); } catch (Exception ex) { e = ex; } } //CalculatePrimeState calcState = new CalculatePrimeState( // numberToTest, // firstDivisor, // isPrime, // e, // TaskCanceled(asyncOp.UserSuppliedState), // asyncOp); //this.CompletionMethod(calcState); this.CompletionMethod( numberToTest, firstDivisor, isPrime, e, TaskCanceled(asyncOp.UserSuppliedState), asyncOp); //completionMethodDelegate(calcState); }
' This method performs the actual prime number computation. ' It is executed on the worker thread. Private Sub CalculateWorker( _ ByVal numberToTest As Integer, _ ByVal asyncOp As AsyncOperation) Dim prime As Boolean = False Dim firstDivisor As Integer = 1 Dim exc As Exception = Nothing ' Check that the task is still active. ' The operation may have been canceled before ' the thread was scheduled. If Not Me.TaskCanceled(asyncOp.UserSuppliedState) Then Try ' Find all the prime numbers up to the ' square root of numberToTest. Dim primes As ArrayList = BuildPrimeNumberList( _ numberToTest, asyncOp) ' Now we have a list of primes less than 'numberToTest. prime = IsPrime( _ primes, _ numberToTest, _ firstDivisor) Catch ex As Exception exc = ex End Try End If Me.CompletionMethod( _ numberToTest, _ firstDivisor, _ prime, _ exc, _ TaskCanceled(asyncOp.UserSuppliedState), _ asyncOp) End Sub
Implémentez
BuildPrimeNumberList
. Elle prend deux paramètres : le nombre à tester et une AsyncOperation. Elle utilise la AsyncOperation pour signaler la progression et les résultats incrémentiels. Cela garantit que les gestionnaires d’événements du client sont appelés sur le thread ou le contexte adapté au modèle d’application. LorsqueBuildPrimeNumberList
trouve un nombre premier, elle le signale comme un résultat incrémentiel au gestionnaire d’événements du client pour l’événementProgressChanged
. Cela requiert une classe dérivée de ProgressChangedEventArgs, appeléeCalculatePrimeProgressChangedEventArgs
, possédant une propriété supplémentaire nomméeLatestPrimeNumber
.Par ailleurs, la méthode
BuildPrimeNumberList
appelle périodiquement la méthodeTaskCanceled
et s’arrête si celle-ci retournetrue
.// This method computes the list of prime numbers used by the // IsPrime method. private ArrayList BuildPrimeNumberList( int numberToTest, AsyncOperation asyncOp) { ProgressChangedEventArgs e = null; ArrayList primes = new ArrayList(); int firstDivisor; int n = 5; // Add the first prime numbers. primes.Add(2); primes.Add(3); // Do the work. while (n < numberToTest && !TaskCanceled( asyncOp.UserSuppliedState ) ) { if (IsPrime(primes, n, out firstDivisor)) { // Report to the client that a prime was found. e = new CalculatePrimeProgressChangedEventArgs( n, (int)((float)n / (float)numberToTest * 100), asyncOp.UserSuppliedState); asyncOp.Post(this.onProgressReportDelegate, e); primes.Add(n); // Yield the rest of this time slice. Thread.Sleep(0); } // Skip even numbers. n += 2; } return primes; }
' This method computes the list of prime numbers used by the ' IsPrime method. Private Function BuildPrimeNumberList( _ ByVal numberToTest As Integer, _ ByVal asyncOp As AsyncOperation) As ArrayList Dim e As ProgressChangedEventArgs = Nothing Dim primes As New ArrayList Dim firstDivisor As Integer Dim n As Integer = 5 ' Add the first prime numbers. primes.Add(2) primes.Add(3) ' Do the work. While n < numberToTest And _ Not Me.TaskCanceled(asyncOp.UserSuppliedState) If IsPrime(primes, n, firstDivisor) Then ' Report to the client that you found a prime. e = New CalculatePrimeProgressChangedEventArgs( _ n, _ CSng(n) / CSng(numberToTest) * 100, _ asyncOp.UserSuppliedState) asyncOp.Post(Me.onProgressReportDelegate, e) primes.Add(n) ' Yield the rest of this time slice. Thread.Sleep(0) End If ' Skip even numbers. n += 2 End While Return primes End Function
Implémentez
IsPrime
. Elle prend trois paramètres : une liste de nombres premiers connus, le nombre à tester et un paramètre de sortie pour le premier diviseur trouvé. Étant donné la liste de nombres premiers, elle détermine si le nombre testé est premier.// This method tests n for primality against the list of // prime numbers contained in the primes parameter. private bool IsPrime( ArrayList primes, int n, out int firstDivisor) { bool foundDivisor = false; bool exceedsSquareRoot = false; int i = 0; int divisor = 0; firstDivisor = 1; // Stop the search if: // there are no more primes in the list, // there is a divisor of n in the list, or // there is a prime that is larger than // the square root of n. while ( (i < primes.Count) && !foundDivisor && !exceedsSquareRoot) { // The divisor variable will be the smallest // prime number not yet tried. divisor = (int)primes[i++]; // Determine whether the divisor is greater // than the square root of n. if (divisor * divisor > n) { exceedsSquareRoot = true; } // Determine whether the divisor is a factor of n. else if (n % divisor == 0) { firstDivisor = divisor; foundDivisor = true; } } return !foundDivisor; }
' This method tests n for primality against the list of ' prime numbers contained in the primes parameter. Private Function IsPrime( _ ByVal primes As ArrayList, _ ByVal n As Integer, _ ByRef firstDivisor As Integer) As Boolean Dim foundDivisor As Boolean = False Dim exceedsSquareRoot As Boolean = False Dim i As Integer = 0 Dim divisor As Integer = 0 firstDivisor = 1 ' Stop the search if: ' there are no more primes in the list, ' there is a divisor of n in the list, or ' there is a prime that is larger than ' the square root of n. While i < primes.Count AndAlso _ Not foundDivisor AndAlso _ Not exceedsSquareRoot ' The divisor variable will be the smallest prime number ' not yet tried. divisor = primes(i) i = i + 1 ' Determine whether the divisor is greater than the ' square root of n. If divisor * divisor > n Then exceedsSquareRoot = True ' Determine whether the divisor is a factor of n. ElseIf n Mod divisor = 0 Then firstDivisor = divisor foundDivisor = True End If End While Return Not foundDivisor End Function
Dérivez
CalculatePrimeProgressChangedEventArgs
de ProgressChangedEventArgs. Cette classe est nécessaire pour signaler des résultats incrémentiels au gestionnaire d’événements du client pour l’événementProgressChanged
. Elle possède une propriété supplémentaire nomméeLatestPrimeNumber
.public class CalculatePrimeProgressChangedEventArgs : ProgressChangedEventArgs { private int latestPrimeNumberValue = 1; public CalculatePrimeProgressChangedEventArgs( int latestPrime, int progressPercentage, object userToken) : base( progressPercentage, userToken ) { this.latestPrimeNumberValue = latestPrime; } public int LatestPrimeNumber { get { return latestPrimeNumberValue; } } }
Public Class CalculatePrimeProgressChangedEventArgs Inherits ProgressChangedEventArgs Private latestPrimeNumberValue As Integer = 1 Public Sub New( _ ByVal latestPrime As Integer, _ ByVal progressPercentage As Integer, _ ByVal UserState As Object) MyBase.New(progressPercentage, UserState) Me.latestPrimeNumberValue = latestPrime End Sub Public ReadOnly Property LatestPrimeNumber() As Integer Get Return latestPrimeNumberValue End Get End Property End Class
Point de contrôle 3
Vous pouvez maintenant générer le composant.
Pour tester le composant :
Compilez le composant.
Il ne reste plus qu’à écrire les méthodes permettant de démarrer et d’annuler des opérations asynchrones,
CalculatePrimeAsync
etCancelAsync
.
Implémenter les méthodes de démarrage et d’annulation
Pour démarrer la méthode de travail sur son propre thread, appelez BeginInvoke
sur le délégué qui l’inclut. Pour gérer la durée de vie d’une opération asynchrone en particulier, vous appellerez la méthode CreateOperation sur la classe d’assistance AsyncOperationManager. Cela retourne un AsyncOperation, qui marshale les appels sur les gestionnaires d'événements du client au thread ou au contexte adéquat.
Pour annuler une opération en attente donnée, appelez PostOperationCompleted sur la AsyncOperation correspondante. Cela met fin à cette opération, et tous les appels ultérieurs à son AsyncOperation lèveront une exception.
Pour implémenter les fonctionnalités de démarrage et d’annulation :
Implémentez la méthode
CalculatePrimeAsync
. Vérifiez que le jeton fourni par le client (ID de tâche) est unique parmi tous les jetons représentant des tâches actuellement en attente. Si le client transmet un jeton non unique,CalculatePrimeAsync
lève une exception. Sinon, le jeton est ajouté à la collection d’ID de tâche.// This method starts an asynchronous calculation. // First, it checks the supplied task ID for uniqueness. // If taskId is unique, it creates a new WorkerEventHandler // and calls its BeginInvoke method to start the calculation. public virtual void CalculatePrimeAsync( int numberToTest, object taskId) { // Create an AsyncOperation for taskId. AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(taskId); // Multiple threads will access the task dictionary, // so it must be locked to serialize access. lock (userStateToLifetime.SyncRoot) { if (userStateToLifetime.Contains(taskId)) { throw new ArgumentException( "Task ID parameter must be unique", "taskId"); } userStateToLifetime[taskId] = asyncOp; } // Start the asynchronous operation. WorkerEventHandler workerDelegate = new WorkerEventHandler(CalculateWorker); workerDelegate.BeginInvoke( numberToTest, asyncOp, null, null); }
' This method starts an asynchronous calculation. ' First, it checks the supplied task ID for uniqueness. ' If taskId is unique, it creates a new WorkerEventHandler ' and calls its BeginInvoke method to start the calculation. Public Overridable Sub CalculatePrimeAsync( _ ByVal numberToTest As Integer, _ ByVal taskId As Object) ' Create an AsyncOperation for taskId. Dim asyncOp As AsyncOperation = _ AsyncOperationManager.CreateOperation(taskId) ' Multiple threads will access the task dictionary, ' so it must be locked to serialize access. SyncLock userStateToLifetime.SyncRoot If userStateToLifetime.Contains(taskId) Then Throw New ArgumentException( _ "Task ID parameter must be unique", _ "taskId") End If userStateToLifetime(taskId) = asyncOp End SyncLock ' Start the asynchronous operation. Dim workerDelegate As New WorkerEventHandler( _ AddressOf CalculateWorker) workerDelegate.BeginInvoke( _ numberToTest, _ asyncOp, _ Nothing, _ Nothing) End Sub
Implémentez la méthode
CancelAsync
. Si le paramètretaskId
existe dans la collection de jetons, il est supprimé. Cela empêche l’exécution des tâches annulées qui n’ont pas démarré. Si la tâche est en cours d’exécution, la méthodeBuildPrimeNumberList
se termine lorsqu’elle détecte que l’ID de tâche a été supprimé de la collection de durées de vie.// This method cancels a pending asynchronous operation. public void CancelAsync(object taskId) { AsyncOperation asyncOp = userStateToLifetime[taskId] as AsyncOperation; if (asyncOp != null) { lock (userStateToLifetime.SyncRoot) { userStateToLifetime.Remove(taskId); } } }
' This method cancels a pending asynchronous operation. Public Sub CancelAsync(ByVal taskId As Object) Dim obj As Object = userStateToLifetime(taskId) If (obj IsNot Nothing) Then SyncLock userStateToLifetime.SyncRoot userStateToLifetime.Remove(taskId) End SyncLock End If End Sub
Point de contrôle 4
Vous pouvez maintenant générer le composant.
Pour tester le composant :
- Compilez le composant.
Le composant PrimeNumberCalculator
est maintenant terminé et prêt à être utilisé.
Vous trouverez un exemple de client qui utilise le composant PrimeNumberCalculator
sur la page Guide pratique : implémenter un client du modèle asynchrone basé sur les événements.
Étapes suivantes
Vous pouvez remplir cet exemple en écrivant CalculatePrime
, l’équivalent synchrone de la méthode CalculatePrimeAsync
. Cela rendra le composant PrimeNumberCalculator
entièrement compatible avec le modèle asynchrone basé sur les événements.
Vous pouvez améliorer cet exemple en conservant la liste de tous les nombres premiers découverts par différents appels pour différents nombres testés. Grâce à cette approche, chaque tâche bénéficiera du travail effectué par les tâches précédentes. Veillez à protéger cette liste par des régions lock
, afin de sérialiser l’accès à la liste par différents threads.
Vous pouvez également améliorer cet exemple en testant des diviseurs triviaux comme 2, 3 et 5.