Procédure pas à pas : implémentation d'un composant qui prend en charge le modèle asynchrone basé sur des événements

Si vous écrivez une classe avec certaines opérations pouvant entraîner d'importants retards, prévoyez de lui affecter des fonctionnalités asynchrones en implémentant Vue d'ensemble du modèle asynchrone basé sur des événements.

Cette procédure pas à pas illustre comment créer un composant qui implémente le modèle asynchrone basé sur des événements. Il est implémenté à l'aide des classes d'assistance de l'espace de noms System.ComponentModel, ce qui garantit que le composant fonctionne correctement sous tous les modèles d'application, notamment ASP.NET, les applications console et les applications Windows Forms. Ce composant est également concevable avec un contrôle PropertyGrid et vos propres concepteurs personnalisés.

Lorsque vous avez terminé, une application calcule des nombres premiers de manière asynchrone. Votre application possède un thread d'interface utilisateur principal et un thread pour chaque calcul de nombre premier. Bien que le fait de tester si un nombre élevé est un nombre premier puisse prendre beaucoup de temps, le thread d'interface utilisateur principal n'est pas interrompu par ce délai et le formulaire est réactif pendant les calculs. Vous pouvez exécuter simultanément autant de calculs que vous le souhaitez et annuler des calculs en attente de manière sélective.

Cette procédure pas à pas illustre les tâches suivantes :

  • Création du composant

  • Définition des événements asynchrones et des délégués publics

  • Définition des délégués privés

  • Implémentation des événements publics

  • Implémentation de la méthode d'achèvement

  • Implémentation des méthodes de travail

  • Implémentation des méthodes Start et Cancel

Pour copier le code dans cette rubrique sous forme de liste unique, consultez Comment : implémenter un composant qui prend en charge le modèle asynchrone basé sur des événements.

Création du composant

La première étape consiste à créer le composant qui implémentera le modèle asynchrone basé sur des événements.

Pour créer le composant

  • Créez une classe appelée PrimeNumberCalculator qui hérite de Component.

Définition des événements asynchrones et des délégués publics

Votre composant communique avec les clients à l'aide des événements. L'événement MethodNameCompleted avertit les clients de l'achèvement d'une tâche asynchrone, et l'événement MethodNameProgressChanged informe les clients de la progression d'une tâche asynchrone.

Pour définir des événements asynchrones pour les clients de votre composant :

  1. Importez les espaces de noms System.Threading et System.Collections.Specialized au début de votre fichier.

    Imports System
    Imports System.Collections
    Imports System.Collections.Specialized
    Imports System.ComponentModel
    Imports System.Drawing
    Imports System.Globalization
    Imports System.Threading
    Imports System.Windows.Forms
    
    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;
    
  2. 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 Sub ProgressChangedEventHandler( _
        ByVal e As ProgressChangedEventArgs)
    
    Public Delegate Sub CalculatePrimeCompletedEventHandler( _
        ByVal sender As Object, _
        ByVal e As CalculatePrimeCompletedEventArgs)
    
    public delegate void ProgressChangedEventHandler(
        ProgressChangedEventArgs e);
    
    public delegate void CalculatePrimeCompletedEventHandler(
        object sender,
        CalculatePrimeCompletedEventArgs e);
    
  3. Dans la définition de la classe PrimeNumberCalculator , déclarez les événements destinés à signaler la progression et l'achèvement aux clients.

    Public Event ProgressChanged _
        As ProgressChangedEventHandler
    Public Event CalculatePrimeCompleted _
        As CalculatePrimeCompletedEventHandler
    
    public event ProgressChangedEventHandler ProgressChanged;
    public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
    
  4. Après la définition de la classe PrimeNumberCalculator , dérivez la classe CalculatePrimeCompletedEventArgs pour signaler le résultat de chaque calcul au gestionnaire d'événements du client pour l'événement CalculatePrimeCompleted. En plus des propriétés AsyncCompletedEventArgs, cette classe permet au client de déterminer le nombre qui a été testé, s'il s'agit d'un nombre premier, et le premier diviseur s'il ne s'agit pas d'un nombre premier.

    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
    
        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;
                }
            }
        }
    
    

Point de contrôle

À présent, vous pouvez générer le composant.

Pour tester votre composant :

  • Compilez le composant.

    Vous recevez 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éfinition 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. La méthode de rappel doit avoir une signature qui accepte un seul paramètre de type Object, ce qui signifie que vous devrez passer l'état aux délégués dans une classe wrapper. Pour plus d'informations, consultez SendOrPostCallback.

Pour implémenter le comportement asynchrone interne de votre composant :

  1. Déclarez et créez les délégués SendOrPostCallback dans la classe PrimeNumberCalculator . Créez les objets SendOrPostCallback dans une méthode utilitaire appelée InitializeDelegates.

    Vous aurez besoin de deux délégués : un pour signaler la progression au client, et un pour signaler l'achèvement au client.

    Private onProgressReportDelegate As SendOrPostCallback
    Private onCompletedDelegate As SendOrPostCallback
    
    
    ...
    
    
    Protected Overridable Sub InitializeDelegates()
        onProgressReportDelegate = _
            New SendOrPostCallback(AddressOf ReportProgress)
        onCompletedDelegate = _
            New SendOrPostCallback(AddressOf CalculateCompleted)
    End Sub
    
    private SendOrPostCallback onProgressReportDelegate;
    private SendOrPostCallback onCompletedDelegate;
    
    
    ...
    
    
    protected virtual void InitializeDelegates()
    {
        onProgressReportDelegate =
            new SendOrPostCallback(ReportProgress);
        onCompletedDelegate =
            new SendOrPostCallback(CalculateCompleted);
    }
    
  2. Appelez la méthode InitializeDelegates dans le constructeur de votre composant.

    Public Sub New()
    
        InitializeComponent()
    
        InitializeDelegates()
    
    End Sub
    
    public PrimeNumberCalculator()
    {   
        InitializeComponent();
    
        InitializeDelegates();
    }
    
  3. Déclarez un délégué dans la classe PrimeNumberCalculator qui gère de manière asynchrone le travail effectif à réaliser. Ce délégué encapsule la méthode de travail qui teste si un nombre est un nombre premier. Le délégué accepte un paramètre AsyncOperation qui sera utilisé pour assurer le suivi de la durée de vie de l'opération asynchrone.

    Private Delegate Sub WorkerEventHandler( _
    ByVal numberToCheck As Integer, _
    ByVal asyncOp As AsyncOperation)
    
    private delegate void WorkerEventHandler(
        int numberToCheck,
        AsyncOperation asyncOp);
    
  4. Créez une collection pour gérer les durées de vie d'opérations asynchrones en attente. Le client a besoin d'une méthode de suivi des opérations au fur et à mesure qu'elles sont exécutées et achevées, et ce suivi est effectué en demandant au client de passer un jeton unique, ou un ID de tâche, lorsqu'il fait appel à la méthode asynchrone. Le composant PrimeNumberCalculator doit conserver une trace de chaque appel en associant l'ID de tâche à l'appel correspondant. Si le client passe un ID de tâche qui n'est pas unique, le composant PrimeNumberCalculator doit lever une exception.

    Le composant PrimeNumberCalculator conserve une trace de l'ID de tâche en utilisant une classe de collection spéciale appelée HybridDictionary. Dans la définition de classe, créez un HybridDictionary appelé userTokenToLifetime.

    Private userStateToLifetime As New HybridDictionary()
    
    private HybridDictionary userStateToLifetime = 
        new HybridDictionary();
    

Implémentation des événements publics

Les composants qui implémentent le modèle asynchrone basé sur des événements communiquent avec les clients à l'aide d'événements. Ces événements sont appelés sur le thread approprié à l'aide de la classe AsyncOperation.

Pour déclencher des événements aux clients de votre composant :

  • Implémentez des événements publics pour le signalement aux 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 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
    
    // 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);
        }
    }
    

Implémentation de la méthode d'achèvement

Le délégué d'achèvement est la méthode que le comportement asynchrone libre de threads sous-jacent appelle lorsque l'opération asynchrone se termine par un achèvement correct, une erreur ou une annulation. Cet appel a lieu sur un thread arbitraire.

Cette méthode correspond à la suppression de l'ID de tâche du client dans la collection interne de jetons de clients uniques. Cette méthode met également fin à la durée de vie d'une opération asynchrone particulière en appelant la méthode PostOperationCompleted sur le AsyncOperation correspondant. Cet appel déclenche l'événement d'achèvement sur le thread approprié au modèle d'application. Une fois la méthode PostOperationCompleted appelée, cette instance de AsyncOperation ne peut plus être utilisée et toute tentative ultérieure visant à 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. Il contient l'état pour le nombre qui a été testé par cette opération asynchrone particulière, sait si le nombre est un nombre premier et contient la valeur de son premier diviseur s'il ne s'agit pas d'un nombre premier. Elle contient également l'état décrivant toute exception levée et le AsyncOperation correspondant à cette tâche particulière.

Pour terminer une opération asynchrone :

  • Implémentez la méthode d'achèvement. Elle accepte six paramètres qu'elle utilise pour remplir CalculatePrimeCompletedEventArgs qui est retourné au client via le CalculatePrimeCompletedEventHandler du client. Elle supprime le jeton d'ID de tâche du client de la collection interne et met fin à la durée de vie de l'opération asynchrone en appelant PostOperationCompleted. AsyncOperation marshale l'appel au thread ou au contexte approprié 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 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
    
    // 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.
    }
    

Point de contrôle

À présent, vous pouvez générer le composant.

Pour tester votre 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émentation des méthodes de travail

Jusqu'à présent, vous avez implémenté le code asynchrone de prise en charge pour le composant PrimeNumberCalculator. Maintenant, vous pouvez implémenter le code qui procède au travail effectif. Vous implémenterez trois méthodes : CalculateWorker, BuildPrimeNumberList et IsPrime. Ensemble, BuildPrimeNumberList et IsPrime composent un algorithme connu appelé crible d'Ératosthène qui détermine si un nombre est un nombre premier en recherchant tous les nombres premiers jusqu'à la racine carrée du nombre test. Si aucun diviseur n'est trouvé à ce stade, le nombre test est un nombre premier.

Si ce composant était conçu pour une efficacité maximale, il rappellerait tous les nombres premiers découverts par divers appels pour différents nombres test. Il rechercherait également des diviseurs simples tels 2, 3 et 5. Cet exemple sert à montrer comment les opérations prenant du temps peuvent être exécutées de façon asynchrone, ces optimisations sont données à titre d'exercice.

La méthode CalculateWorker est encapsulée dans un délégué et est appelée de manière asynchrone avec un appel à BeginInvoke.

RemarqueRemarque

Le rapport de progression est implémenté dans la méthode BuildPrimeNumberList.Sur les ordinateurs rapides, les événements ProgressChanged peuvent être déclenchés à intervalles courts.Le thread client sur lequel ces événements sont déclenchés doit être capable de gérer cette situation.Le code de l'interface utilisateur peut être submergé de messages et incapable d'y faire face, ce qui provoque un blocage.Pour obtenir un exemple d'interface utilisateur gérant cette situation, consultez Comment : implémenter un client du modèle asynchrone basé sur des événements.

Pour exécuter le calcul de nombre premier de manière asynchrone :

  1. Implémentez la méthode utilitaire TaskCanceled. Elle vérifie la collection de durées de vie de la tâche pour l'ID de tâche donné et retourne la valeur true si l'ID de tâche est introuvable.

    ' 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
    
    // Utility method for determining if a 
    // task has been canceled.
    private bool TaskCanceled(object taskId)
    {
        return( userStateToLifetime[taskId] == null );
    }
    
  2. Implémentez la méthode CalculateWorker. Elle accepte 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 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
    
    // 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);
    }
    
  3. Implémentez BuildPrimeNumberList. Elle accepte deux paramètres : le nombre à tester et une AsyncOperation. Elle utilise 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 approprié pour le modèle d'application. Lorsque BuildPrimeNumberList trouve un nombre premier, celui-ci est signalé comme un résultat incrémentiel au gestionnaire d'événements du client pour l'événement ProgressChanged. Une classe dérivée de ProgressChangedEventArgs, appelée CalculatePrimeProgressChangedEventArgs, à laquelle une propriété appelée LatestPrimeNumber est ajoutée, est nécessaire.

    La méthode BuildPrimeNumberList appelle aussi périodiquement la méthode TaskCanceled et s'arrête si la méthode retourne la valeur true.

    ' 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
    
    // 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;
    }
    
  4. Implémentez IsPrime. Elle accepte trois paramètres : une liste de nombres premiers connus, le nombre à tester et un paramètre de sortie pour le premier diviseur trouvé. Selon la liste de nombres premiers, elle détermine si le nombre test est un nombre premier.

    ' 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
    
    // 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;
    }
    
  5. 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énement ProgressChanged. Elle possède une propriété ajoutée appelée LatestPrimeNumber.

    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
    
    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;
            }
        }
    }
    

Point de contrôle

À présent, vous pouvez générer le composant.

Pour tester votre composant :

  • Compilez le composant.

    Il reste alors à écrire les méthodes permettant de démarrer et d'annuler des opérations asynchrones, à savoir CalculatePrimeAsync et CancelAsync.

Implémentation des méthodes Start et Cancel

Vous démarrez la méthode de travail sur son propre thread en appelant BeginInvoke sur le délégué qui l'encapsule. Pour gérer la durée de vie d'une opération asynchrone particulière, vous appelez la méthode CreateOperation sur la classe d'assistance AsyncOperationManager. Elle retourne une AsyncOperation qui marshale les appels sur les gestionnaires d'événements du client au thread ou au contexte approprié.

Vous annulez une opération en attente particulière en appelant PostOperationCompleted sur son 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 Start et Cancel :

  1. Implémentez la méthode CalculatePrimeAsync. Assurez-vous que le jeton (ID de tâche) fourni par le client est unique parmi tous les jetons représentant les tâches en attente. Si le client passe un jeton qui n'est pas 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 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
    
    // 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);
    }
    
  2. Implémentez la méthode CancelAsync. Si le paramètre taskId 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éthode BuildPrimeNumberList s'arrête 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 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
    
    // 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);
            }
        }
    }
    

Point de contrôle

À présent, vous pouvez générer le composant.

Pour tester votre composant :

  • Compilez le composant.

Le composant PrimeNumberCalculator est maintenant terminé et prêt à être utilisé.

Pour obtenir un exemple de client qui utilise le composant PrimeNumberCalculator, consultez Comment : implémenter un client du modèle asynchrone basé sur des événements.

Étapes suivantes

Vous pouvez remplir cet exemple en écrivant CalculatePrime, l'équivalent synchrone de la méthode CalculatePrimeAsync. Le composant PrimeNumberCalculator devient alors totalement conforme au modèle asynchrone basé sur des événements.

Vous pouvez améliorer cet exemple en conservant la liste de tous les nombres premiers découverts par divers appels pour différents nombres test. En utilisant 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 zones lock afin que l'accès à la liste par différents threads soit sérialisé.

Vous pouvez également améliorer cet exemple en testant les diviseurs triviaux tels que 2, 3 et 5.

Voir aussi

Tâches

Comment : exécuter une opération en arrière-plan

Comment : implémenter un composant qui prend en charge le modèle asynchrone basé sur des événements

Concepts

Vue d'ensemble du modèle asynchrone basé sur des événements

Autres ressources

Multithreading in Visual Basic

Programmation multithread avec le modèle asynchrone basé sur les événements