Dela via


Anvisningar: Implementera en komponent som stöder det händelsebaserade asynkrona mönstret

Om du skriver en klass med vissa åtgärder som kan medföra märkbara fördröjningar kan du överväga att ge den asynkrona funktioner genom att implementera den händelsebaserade asynkrona mönsteröversikten.

Den här genomgången illustrerar hur du skapar en komponent som implementerar det händelsebaserade asynkrona mönstret. Den implementeras med hjälpklasser från System.ComponentModel namnområdet, vilket säkerställer att komponenten fungerar korrekt under alla programmodeller, inklusive ASP.NET, konsolprogram och Windows Forms program. Den här komponenten är också designbar med en PropertyGrid kontroll och dina egna anpassade designers.

När du är igenom har du ett program som beräknar primtal asynkront. Ditt program har en tråd för huvudanvändargränssnittet (UI) och en tråd för varje beräkning av primtal. Det kan ta en märkbar tid att testa om ett stort tal är prime, men huvudtråden i användargränssnittet avbryts inte av den här fördröjningen och formuläret svarar under beräkningarna. Du kommer att kunna köra så många beräkningar som du vill samtidigt och selektivt avbryta väntande beräkningar.

Uppgifter som illustreras i den här genomgången är:

  • Skapa komponenten

  • Definiera offentliga asynkrona händelser och ombud

  • Definiera privata ombud

  • Implementera offentliga händelser

  • Implementera slutförandemetoden

  • Implementera arbetsmetoderna

  • Implementera start- och avbrutna metoder

Information om hur du kopierar koden i det här avsnittet som en enda lista finns i How to: Implement a Client of the Event-based Asynchronous Pattern (Så här implementerar du en klient för det händelsebaserade asynkrona mönstret).

Skapa komponenten

Det första steget är att skapa komponenten som implementerar det händelsebaserade asynkrona mönstret.

Så här skapar du komponenten

  • Skapa en klass med namnet PrimeNumberCalculator som ärver från Component.

Definiera offentliga asynkrona händelser och ombud

Komponenten kommunicerar med klienter med hjälp av händelser. Händelsen MethodNameCompleted aviserar klienter till slutförandet av en asynkron aktivitet, och händelsen MethodNameProgressChanged informerar klienterna om förloppet för en asynkron aktivitet.

Så här definierar du asynkrona händelser för klienter i komponenten:

  1. Importera namnrymderna System.Threading och System.Collections.Specialized överst i filen.

    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
    
  2. Före klassdefinitionen PrimeNumberCalculator deklarerar du ombud för förlopps- och slutförandehändelser.

    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)
    
  3. I klassdefinitionen PrimeNumberCalculator deklarerar du händelser för att rapportera förlopp och slutförande till klienter.

    public event ProgressChangedEventHandler ProgressChanged;
    public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
    
    Public Event ProgressChanged _
        As ProgressChangedEventHandler
    Public Event CalculatePrimeCompleted _
        As CalculatePrimeCompletedEventHandler
    
  4. Efter klassdefinitionen PrimeNumberCalculatorCalculatePrimeCompletedEventArgs härleder du klassen för att rapportera resultatet av varje beräkning till klientens händelsehanterare för CalculatePrimeCompleted.-händelsen. Förutom egenskaperna gör den AsyncCompletedEventArgs här klassen det möjligt för klienten att avgöra vilket tal som testades, om det är prime och vad den första nämnaren är om den inte är prime.

    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
    

Kontrollpunkt 1

Nu kan du skapa komponenten.

Testa komponenten

  • Kompilera komponenten.

    Du får två kompilatorvarningar:

    warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.ProgressChanged' is never used  
    warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.CalculatePrimeCompleted' is never used  
    

    Varningarna tas bort i nästa avsnitt.

Definiera privata ombud

De asynkrona aspekterna av komponenten PrimeNumberCalculator implementeras internt med ett särskilt ombud som SendOrPostCallbackkallas . En SendOrPostCallback representerar en motringningsmetod som körs på en ThreadPool tråd. Motringningsmetoden måste ha en signatur som tar en enda parameter av typen Object, vilket innebär att du måste skicka tillstånd mellan ombud i en omslutningsklass. Mer information finns i SendOrPostCallback.

Implementera komponentens interna asynkrona beteende:

  1. Deklarera och skapa ombuden SendOrPostCallbackPrimeNumberCalculator i klassen . Skapa objekten SendOrPostCallback i en verktygsmetod med namnet InitializeDelegates.

    Du behöver två ombud: en för att rapportera förloppet till klienten och en för att rapportera slutförande till klienten.

    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
    
  2. InitializeDelegates Anropa metoden i komponentens konstruktor.

    public PrimeNumberCalculator()
    {
        InitializeComponent();
    
        InitializeDelegates();
    }
    
    Public Sub New()
    
        InitializeComponent()
    
        InitializeDelegates()
    
    End Sub
    
  3. Deklarera ett ombud i PrimeNumberCalculator klassen som hanterar det faktiska arbete som ska utföras asynkront. Det här ombudet omsluter arbetsmetoden som testar om ett tal är prime. Ombudet tar en AsyncOperation parameter som används för att spåra livslängden för den asynkrona åtgärden.

    private delegate void WorkerEventHandler(
        int numberToCheck,
        AsyncOperation asyncOp);
    
    Private Delegate Sub WorkerEventHandler( _
    ByVal numberToCheck As Integer, _
    ByVal asyncOp As AsyncOperation)
    
  4. Skapa en samling för att hantera livslängder för väntande asynkrona åtgärder. Klienten behöver ett sätt att spåra åtgärder när de körs och slutförs, och den här spårningen görs genom att kräva att klienten skickar en unik token, eller aktivitets-ID, när klienten gör anropet till den asynkrona metoden. Komponenten PrimeNumberCalculator måste hålla reda på varje anrop genom att associera aktivitets-ID:t med motsvarande anrop. Om klienten skickar ett aktivitets-ID som inte är unikt måste komponenten PrimeNumberCalculator generera ett undantag.

    Komponenten PrimeNumberCalculator håller reda på aktivitets-ID:t med hjälp av en särskild samlingsklass som kallas HybridDictionary. I klassdefinitionen skapar du en HybridDictionary med namnet userStateToLifetime.

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

Implementera offentliga händelser

Komponenter som implementerar det händelsebaserade asynkrona mönstret kommunicerar med klienter med hjälp av händelser. Dessa händelser anropas i rätt tråd med hjälp av AsyncOperation klassen .

Så här genererar du händelser till komponentens klienter:

  1. Implementera offentliga händelser för rapportering till klienter. Du behöver en händelse för att rapportera förloppet och en för att slutföra rapporteringen.

    // 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
    

Implementera slutförandemetoden

Slutförandedelegaten är den metod som det underliggande, fritt trådade asynkrona beteendet anropar när den asynkrona åtgärden avslutas genom slutförande, fel eller annullering. Det här anropet sker på en godtycklig tråd.

Den här metoden är den metod där klientens aktivitets-ID tas bort från den interna samlingen med unika klienttoken. Den här metoden avslutar också livslängden för en viss asynkron åtgärd genom att anropa PostOperationCompleted metoden på motsvarande AsyncOperation. Det här anropet genererar slutförandehändelsen på den tråd som är lämplig för programmodellen. PostOperationCompleted När metoden har anropats kan den här instansen av AsyncOperation inte längre användas, och eventuella efterföljande försök att använda den genererar ett undantag.

Signaturen CompletionMethod måste innehålla allt tillstånd som krävs för att beskriva resultatet av den asynkrona åtgärden. Den innehåller tillstånd för talet som testades av den här asynkrona åtgärden, om talet är prime och värdet för dess första divisor om det inte är ett primtal. Den innehåller också tillstånd som beskriver eventuella undantag som har inträffat och som motsvarar den AsyncOperation här specifika aktiviteten.

Så här slutför du en asynkron åtgärd:

  • Implementera slutförandemetoden. Det tar sex parametrar, som används för att fylla i en CalculatePrimeCompletedEventArgs som returneras till klienten via klientens CalculatePrimeCompletedEventHandler. Den tar bort klientens aktivitets-ID-token från den interna samlingen och avslutar den asynkrona åtgärdens livslängd med ett anrop till PostOperationCompleted. Marskalkar AsyncOperation anropet till den tråd eller kontext som är lämplig för programmodellen.

    // 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
    

Kontrollpunkt 2

Nu kan du skapa komponenten.

Testa komponenten

  • Kompilera komponenten.

    Du får en kompilatorvarning:

    warning CS0169: The private field 'AsynchronousPatternExample.PrimeNumberCalculator.workerDelegate' is never used  
    

    Den här varningen löses i nästa avsnitt.

Implementera arbetsmetoderna

Hittills har du implementerat den asynkrona stödkoden för komponenten PrimeNumberCalculator . Nu kan du implementera koden som utför det faktiska arbetet. Du implementerar tre metoder: CalculateWorker, BuildPrimeNumberListoch IsPrime. BuildPrimeNumberList Tillsammans och IsPrime utgör en välkänd algoritm som kallas Sieve of Eratosthenes, som avgör om ett tal är prime genom att hitta alla primtal upp till kvadratroten av testnumret. Om inga divisorer hittas vid den tidpunkten är testnumret prime.

Om den här komponenten skrevs för maximal effektivitet skulle den komma ihåg alla primtal som identifierats av olika anrop för olika testnummer. Det skulle också söka efter triviala divisorer som 2, 3 och 5. Avsikten med det här exemplet är att visa hur tidskrävande åtgärder kan köras asynkront, så att dessa optimeringar lämnas som en övning åt dig.

Metoden CalculateWorker omsluts i ett ombud och anropas asynkront med ett anrop till BeginInvoke.

Anteckning

Förloppsrapportering implementeras i BuildPrimeNumberList metoden . På snabba datorer ProgressChanged kan händelser aktiveras i snabb följd. Klienttråden, där dessa händelser aktiveras, måste kunna hantera den här situationen. Koden i användargränssnittet kan översvämmas av meddelanden och kan inte hänga med, vilket resulterar i att användaren inte svarar. Ett exempel på ett användargränssnitt som hanterar den här situationen finns i Så här implementerar du en klient för det händelsebaserade asynkrona mönstret.

Så här kör du prime number-beräkningen asynkront:

  1. Implementera verktygsmetoden TaskCanceled . Detta kontrollerar uppgiftslivslängdssamlingen för det angivna aktivitets-ID:t och returnerar true om aktivitets-ID:t inte hittas.

    // 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
    
  2. CalculateWorker Implementera metoden. Det tar två parametrar: ett tal att testa och en 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
    
  3. Implementera BuildPrimeNumberList. Det tar två parametrar: talet som ska testas och en AsyncOperation. Den använder AsyncOperation för att rapportera förlopp och inkrementella resultat. Detta säkerställer att klientens händelsehanterare anropas på rätt tråd eller kontext för programmodellen. När BuildPrimeNumberList hittar ett primärnummer rapporterar det detta som ett inkrementellt resultat till klientens händelsehanterare för ProgressChanged händelsen. Detta kräver en klass som härleds från ProgressChangedEventArgs, med namnet CalculatePrimeProgressChangedEventArgs, som har en tillagd egenskap med namnet LatestPrimeNumber.

    Metoden BuildPrimeNumberList anropar TaskCanceled också metoden regelbundet och avslutas om metoden returnerar true.

    // 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
    
  4. Implementera IsPrime. Det tar tre parametrar: en lista över kända primtal, talet som ska testas och en utdataparameter för den första divisorn som hittades. Med tanke på listan över primtal avgör den om testnumret är primärt.

    // 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
    
  5. Härled CalculatePrimeProgressChangedEventArgs från ProgressChangedEventArgs. Den här klassen är nödvändig för att rapportera inkrementella resultat till klientens händelsehanterare för ProgressChanged händelsen. Den har en tillagd egenskap med namnet LatestPrimeNumber.

    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
    

Kontrollpunkt 3

Nu kan du skapa komponenten.

Testa komponenten

  • Kompilera komponenten.

    Allt som återstår att skriva är metoderna för att starta och avbryta asynkrona åtgärder och CalculatePrimeAsyncCancelAsync.

Implementera start- och avbrutna metoder

Du startar arbetsmetoden i en egen tråd genom att anropa BeginInvoke ombudet som omsluter den. Om du vill hantera livslängden för en viss asynkron åtgärd anropar CreateOperation du metoden i hjälpklassen AsyncOperationManager . Detta returnerar en AsyncOperation, som anropar klientens händelsehanterare till rätt tråd eller kontext.

Du avbryter en viss väntande åtgärd genom att anropa PostOperationCompleted motsvarande AsyncOperation. Detta avslutar den åtgärden, och eventuella efterföljande anrop till den AsyncOperation utlöser ett undantag.

Så här implementerar du funktionerna Start och Avbryt:

  1. CalculatePrimeAsync Implementera metoden. Kontrollera att token som tillhandahålls av klienten (aktivitets-ID) är unik för alla token som representerar väntande aktiviteter. Om klienten skickar in en icke-unik token CalculatePrimeAsync genererar ett undantag. Annars läggs token till i uppgifts-ID-samlingen.

    // 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
    
  2. CancelAsync Implementera metoden. Om parametern taskId finns i tokensamlingen tas den bort. Detta förhindrar att avbrutna aktiviteter som inte har startats körs. Om aktiviteten körs BuildPrimeNumberList avslutas metoden när den identifierar att aktivitets-ID:t har tagits bort från livstidssamlingen.

    // 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
    

Kontrollpunkt 4

Nu kan du skapa komponenten.

Testa komponenten

  • Kompilera komponenten.

Komponenten PrimeNumberCalculator är nu klar och redo att användas.

En exempelklient som använder komponenten PrimeNumberCalculator finns i Så här implementerar du en klient för det händelsebaserade asynkrona mönstret.

Nästa steg

Du kan fylla i det här exemplet genom att skriva CalculatePrime, den synkrona motsvarigheten CalculatePrimeAsync till metoden. Detta gör att komponenten PrimeNumberCalculator är helt kompatibel med det händelsebaserade asynkrona mönstret.

Du kan förbättra det här exemplet genom att behålla listan över alla primtal som identifierats av olika anrop för olika testnummer. Med den här metoden drar varje uppgift nytta av det arbete som utförts av tidigare uppgifter. Var noga med att skydda den här listan med lock regioner, så åtkomst till listan med olika trådar serialiseras.

Du kan också förbättra det här exemplet genom att testa för triviala divisorer, till exempel 2, 3 och 5.

Se även