Vorgehensweise: Implementieren von Komponenten, die das ereignisbasierte asynchrone Muster unterstützen

Wenn Sie eine Klasse mit Vorgängen schreiben, die nennenswerte Verzögerungen verursachen können, sollten Sie die Klasse mit einer asynchronen Funktionalität ausstatten, indem Sie das ereignisbasierte asynchrone Muster implementieren. Informationen zu diesem Muster finden Sie unter Übersicht über ereignisbasierte asynchrone Muster.

Diese exemplarische Vorgehensweise veranschaulicht das Erstellen einer Komponente, die das ereignisbasierte asynchrone Muster implementiert. Dieses Muster wird mithilfe von Hilfsklassen aus dem System.ComponentModel-Namespace implementiert, wodurch eine einwandfreie Funktionsweise der Komponente unter jedem beliebigen Anwendungsmodell gewährleistet wird, z. B. ASP.NET, Konsolenanwendungen und Windows Forms-Anwendungen. Diese Komponente kann auch mit einem PropertyGrid-Steuerelement und Ihren eigenen Designern gestaltet werden.

Wenn Sie diese exemplarische Vorgehensweise abgeschlossen haben, verfügen Sie über eine Anwendung, die Primzahlen asynchron berechnet. Ihre Anwendung wird einen Thread für die Hauptbenutzeroberfläche und einen Thread für jede Primzahlenberechnung haben. Obwohl das Testen, ob eine große Zahl eine Primzahl ist, merklich Zeit in Anspruch nimmt, wird der Thread der Hauptbenutzeroberfläche nicht durch diese Verzögerung unterbrochen, und das Formular behält während der Berechnung seine Reaktionsfähigkeit. Sie können beliebig viele Berechnungen gleichzeitig ausführen und ausstehende Berechnungen selektiv abbrechen.

In dieser exemplarischen Vorgehensweise werden u. a. folgende Aufgaben veranschaulicht:

  • Erstellen der Komponente

  • Definieren von öffentlichen asynchronen Ereignissen und Delegaten

  • Definieren von privaten Delegaten

  • Implementieren von öffentlichen Ereignissen

  • Implementieren der Abschlussmethode

  • Implementieren der Workermethoden

  • Implementieren von Methoden zum Starten und Abbrechen

Informationen zum Kopieren des Codes in diesem Thema als einzelne Auflistung finden Sie unter Gewusst wie: Implementieren eines Clients des ereignisbasierten asynchronen Musters.

Erstellen der Komponente

Der erste Schritt besteht darin, die Komponente zu erstellen, die das ereignisbasierte asynchrone Muster implementiert.

So erstellen Sie die Komponente

  • Erstellen Sie eine Klasse namens PrimeNumberCalculator, die von Component erbt.

Definieren von öffentlichen asynchronen Ereignissen und Delegaten

Die Komponente kommuniziert mit Clients mithilfe von Ereignissen. Das Ereignis MethodNameCompleted warnt Clients bei Abschluss eines asynchronen Tasks, und das Ereignis MethodNameProgressChanged informiert Clients über den Fortschritt eines asynchronen Tasks.

So definieren Sie asynchrone Ereignisse für Clients der Komponente

  1. Importieren Sie die Namespaces System.Threading und System.Collections.Specialized am Anfang der Datei.

    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. Deklarieren Sie vor der Klassendefinition PrimeNumberCalculator Delegate für Fortschritts- und Abschlussereignisse.

    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. Deklarieren Sie in der Klassendefinition PrimeNumberCalculator Ereignisse, um Clients über Fortschritt und Abschluss zu informieren.

    public event ProgressChangedEventHandler ProgressChanged;
    public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
    
    Public Event ProgressChanged _
        As ProgressChangedEventHandler
    Public Event CalculatePrimeCompleted _
        As CalculatePrimeCompletedEventHandler
    
  4. Leiten Sie nach der Klassendefinition PrimeNumberCalculator die Klasse CalculatePrimeCompletedEventArgs ab, um den Ereignishandler des Clients über die Ergebnisse jeder Berechnung für das CalculatePrimeCompleted.event zu informieren. Zusätzlich zu den AsyncCompletedEventArgs-Eigenschaften bietet diese Klasse dem Client die Möglichkeit, die getesteten Zahlen, ob diese Primzahlen sind und deren ersten Divisor, wenn es keine Primzahl ist, zu ermitteln.

    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
    

Prüfpunkt 1

An diesem Punkt können Sie die Komponente erstellen.

So testen Sie Ihre Komponente

  • Kompilieren Sie die Komponente.

    Sie erhalten zwei Compilerwarnungen:

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

    Diese Warnungen werden im nächsten Abschnitt gelöscht.

Definieren von privaten Delegaten

Die asynchronen Aspekte der Komponente PrimeNumberCalculator werden intern mit einem speziellen Delegaten, dem sogenannten SendOrPostCallback, implementiert. Ein SendOrPostCallback stellt eine Rückrufmethode dar, die für einen ThreadPool-Thread ausgeführt wird. Die Rückrufmethode muss über eine Signatur verfügen, die einen einzelnen Parameter vom Typ Object enthält, was bedeutet, dass Sie den Status zwischen Delegaten in einer Wrapperklasse übergeben müssen. Weitere Informationen finden Sie unter SendOrPostCallback.

So implementieren Sie internes asynchrones Verhalten der Komponente

  1. Deklarieren und erstellen Sie die SendOrPostCallback-Delegate in der Klasse PrimeNumberCalculator. Erstellen Sie die SendOrPostCallback-Objekte in der Hilfsprogrammmethode InitializeDelegates.

    Sie benötigen zwei Delegate: einen, um dem Client den Fortschritt, und einen, um dem Client den Abschluss zu melden.

    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. Rufen Sie die InitializeDelegates-Methode im Konstruktor der Komponente auf.

    public PrimeNumberCalculator()
    {
        InitializeComponent();
    
        InitializeDelegates();
    }
    
    Public Sub New()
    
        InitializeComponent()
    
        InitializeDelegates()
    
    End Sub
    
  3. Deklarieren Sie einen Delegaten in der Klasse PrimeNumberCalculator, der die eigentliche, asynchron auszuführende Arbeit übernimmt. Dieser Delegat umschließt die Workermethode, die testet, ob eine Zahl eine Primzahl ist. Der Delegat nutzt einen AsyncOperation-Parameter, mit dem die Lebensdauer des asynchronen Vorgangs verfolgt wird.

    private delegate void WorkerEventHandler(
        int numberToCheck,
        AsyncOperation asyncOp);
    
    Private Delegate Sub WorkerEventHandler( _
    ByVal numberToCheck As Integer, _
    ByVal asyncOp As AsyncOperation)
    
  4. Erstellen Sie eine Auflistung, um die Lebensdauer ausstehender asynchroner Vorgänge zu verwalten. Der Client muss die Möglichkeit haben, Vorgänge zu verfolgen, während sie ausgeführt und abgeschlossen werden. Diese Verfolgung erfolgt dadurch, dass der Client ein eindeutiges Token oder eine Task-ID übergeben muss, sobald er die asynchrone Methode aufruft. Die Komponente PrimeNumberCalculator muss jeden Aufruf verfolgen, indem sie die Task-ID mit dem entsprechenden Aufruf verknüpft. Wenn der Client eine Task-ID übergibt, die nicht eindeutig ist, muss die Komponente PrimeNumberCalculator eine Ausnahme auslösen.

    Die Komponente PrimeNumberCalculator verfolgt die Task-ID mithilfe der speziellen Auflistungsklasse HybridDictionary. Erstellen Sie in der Klassendefinition ein HybridDictionary namens userStateToLifetime.

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

Implementieren von öffentlichen Ereignissen

Komponenten, die das ereignisbasierte asynchrone Muster implementieren, kommunizieren mit Clients mithilfe von Ereignissen. Diese Ereignisse werden auf dem richtigen Thread mithilfe der Klasse AsyncOperation aufgerufen.

So lösen Sie Ereignisse für die Komponente Ihres Clients aus

  1. Implementieren Sie öffentliche Ereignisse für die Meldung an Clients. Sie benötigen ein Ereignis, um den Fortschritt, und eins, um den Abschluss zu melden.

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

Implementieren der Abschlussmethode

Der Abschlussdelegat ist die Methode, die das zugrundeliegende asynchrone Verhalten mit dem Threadingmodell „Free“ aufruft, sobald der asynchrone Vorgang durch erfolgreichen Abschluss, Fehler oder Abbruch beendet wird. Dieser Aufruf erfolgt auf einen beliebigen Thread.

Bei dieser Methode wird die Task-ID des Clients aus der internen Auflistung eindeutiger Clienttoken entfernt. Diese Methode beendet auch die Lebensdauer eines bestimmten asynchronen Vorgangs, indem die Methode PostOperationCompleted für den entsprechenden AsyncOperation aufgerufen wird. Dieser Aufruf löst das Abschlussereignis für den Thread aus, der für das Anwendungsmodell geeignet ist. Nach dem Aufruf der Methode PostOperationCompleted kann diese Instanz von AsyncOperation nicht mehr verwendet werden, und alle nachfolgenden Verwendungsversuche werden eine Ausnahme auslösen.

Die Signatur CompletionMethod muss alle Zustände enthalten, die erforderlich sind, um das Ergebnis des asynchronen Vorgangs zu beschreiben. Sie umfasst den Zustand für die Zahl, für die mit diesem bestimmten asynchronen Vorgang geprüft wurde, ob sie eine Primzahl ist, und den Wert ihres ersten Divisors, wenn es sich nicht um eine Primzahl handelt. Außerdem enthält Sie den Zustand, der alle aufgetretenen Ausnahmen beschreibt, und die entsprechende AsyncOperation für diesen bestimmten Task.

So schließen Sie einen asynchronen Vorgang ab

  • Implementieren Sie die Abschlussmethode. Sie nutzt sechs Parameter, die zum Ausfüllen von CalculatePrimeCompletedEventArgs verwendet werden, welches durch den CalculatePrimeCompletedEventHandler des Clients an den Client zurückgegeben wird. Das Task-ID-Token des Clients wird aus der internen Auflistung entfernt, und die Lebensdauer des asynchronen Vorgangs wird mit einem Aufruf von PostOperationCompleted beendet. AsyncOperation marshallt den Aufruf des Threads bzw. des Kontexts, der für das Anwendungsmodell geeignet ist.

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

Prüfpunkt 2

An diesem Punkt können Sie die Komponente erstellen.

So testen Sie Ihre Komponente

  • Kompilieren Sie die Komponente.

    Sie erhalten eine Compilerwarnung:

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

    Diese Warnung wird im nächsten Abschnitt aufgelöst.

Implementieren der Workermethoden

Bisher haben Sie den unterstützenden asynchronen Code für die Komponente PrimeNumberCalculator implementiert. Jetzt können Sie den Code implementieren, der die eigentliche Arbeit übernimmt. Sie implementieren drei Methoden: CalculateWorker, BuildPrimeNumberList und IsPrime. Zusammen umfassen BuildPrimeNumberList und IsPrime einen bekannten Algorithmus namens „Sieb des Eratosthenes“, der bestimmt, ob eine Zahl eine Primzahl ist, indem er alle Primzahlen bis zur Quadratwurzel der Testzahl findet. Wenn keine Divisoren bis dahin gefunden werden, ist die zu testende Zahl eine Primzahl.

Wenn diese Komponente für maximale Effizienz geschrieben würde, würde sie alle Primzahlen speichern, die durch verschiedene Aufrufe für verschiedene Testzahlen entdeckt wurden. Zudem würde sie auch triviale Divisoren wie 2, 3 und 5 überprüfen. In diesem Beispiel soll demonstriert werden, wie zeitaufwendige Vorgänge asynchron ausgeführt werden können, sodass diese Optimierungen als Übung für Sie belassen werden.

Die CalculateWorker-Methode wird von einem Delegaten umschlossen und mit einem Aufruf von BeginInvoke asynchron aufgerufen.

Hinweis

Die Fortschrittsmeldung wird in der BuildPrimeNumberList-Methode implementiert. Auf schnellen Computern können ProgressChanged-Ereignisse in rascher Folge ausgelöst werden. Der Clientthread, für den diese Ereignisse ausgelöst werden, muss diese Situation verarbeiten können. Der Code der Benutzeroberfläche erhält möglicherweise eine Flut von Meldungen, die er nicht bewältigen kann, sodass keine Reaktion mehr erfolgt. Ein Beispiel für eine Benutzeroberfläche in einer solchen Situation finden Sie unter Gewusst wie: Implementieren eines Clients des ereignisbasierten asynchronen Musters.

So führen Sie die asynchrone Berechnung der Primzahl aus

  1. Implementieren Sie die Hilfsprogrammmethode TaskCanceled. Diese überprüft die Auflistung der Tasklebensdauer für die angegebene Task-ID und gibt true zurück, wenn die Task-ID nicht gefunden wird.

    // 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. Implementieren Sie die CalculateWorker-Methode. Es werden zwei Parameter akzeptiert: eine zu testende Zahl und eine AsyncOperation-Klasse.

    // 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. Implementieren Sie BuildPrimeNumberList. Es werden zwei Parameter akzeptiert: die zu testende Zahl und eine AsyncOperation-Klasse. Die AsyncOperation-Klasse wird verwendet, um den Fortschritt und inkrementelle Ergebnisse zu melden. Dadurch wird sichergestellt, dass die Ereignishandler des Clients für den richtigen Thread oder Kontext für das Anwendungsmodell aufgerufen werden. Wenn BuildPrimeNumberList eine Primzahl findet, wird dies als inkrementelles Ergebnis an den Clientereignishandler für das ProgressChanged-Ereignis weitergegeben. Dies erfordert eine von ProgressChangedEventArgs abgeleitete Klasse namens CalculatePrimeProgressChangedEventArgs, die über eine hinzugefügte Eigenschaft mit der Bezeichnung LatestPrimeNumber verfügt.

    Die BuildPrimeNumberList-Methode ruft in regelmäßigen Abständen die TaskCanceled-Methode auf und wird beendet, wenn die Methode true zurückgibt.

    // 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. Implementieren Sie IsPrime. Es werden drei Parameter akzeptiert: eine Liste der bekannten Primzahlen, die zu testende Zahl und ein Ausgabeparameter für den ersten gefundenen Divisor. Anhand der vorliegenden Primzahlenliste wird ermittelt, ob es sich bei der zu testenden Zahl um eine Primzahl handelt.

    // 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. Leiten Sie CalculatePrimeProgressChangedEventArgs aus ProgressChangedEventArgs ab. Diese Klasse ist notwendig, damit der Clientereignishandler für das ProgressChanged-Ereignis über inkrementelle Ergebnisse informiert wird. Sie verfügt über eine zusätzliche Eigenschaft namens 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
    

Prüfpunkt 3

An diesem Punkt können Sie die Komponente erstellen.

So testen Sie Ihre Komponente

  • Kompilieren Sie die Komponente.

    Was nun noch geschrieben werden muss, sind die Methoden zum Starten und Abbrechen der asynchronen Vorgänge CalculatePrimeAsync und CancelAsync.

Implementieren von Methoden zum Starten und Abbrechen

Sie starten die Workermethode auf ihrem eigenen Thread, indem Sie BeginInvoke über den Delegaten aufrufen, der sie umschließt. Um die Lebensdauer eines bestimmten asynchronen Vorgangs zu verwalten, rufen Sie die CreateOperation-Methode für die Hilfsprogrammklasse AsyncOperationManager auf. Dadurch wird eine AsyncOperation-Klasse zurückgegeben, die Aufrufe für den Ereignishandler des Clients zum richtigen Thread oder Kontext marshallt.

Sie brechen einen bestimmten ausstehenden Vorgang ab, indem Sie PostOperationCompleted für die entsprechende AsyncOperation-Klasse aufrufen. Dadurch wird dieser Vorgang beendet, und alle nachfolgenden Aufrufe für AsyncOperation werden eine Ausnahme auslösen.

So implementieren Sie die Funktionen zum Starten und Abbrechen

  1. Implementieren Sie die CalculatePrimeAsync-Methode. Stellen Sie sicher, dass das vom Client bereitgestellte Token (Task-ID) in Bezug auf alle Tokens der derzeit ausstehenden Tasks eindeutig ist. Wenn der Client ein nicht eindeutiges Token übergibt, löst CalculatePrimeAsync eine Ausnahme aus. Andernfalls wird das Token zur Task-ID-Auflistung hinzugefügt.

    // 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. Implementieren Sie die CancelAsync-Methode. Wenn der taskId-Parameter in der Tokenauflistung vorhanden ist, wird er entfernt. Dies verhindert, dass noch nicht ausgeführte Tasks abgebrochen werden. Wenn der Task ausgeführt wird, wird er durch die BuildPrimeNumberList-Methode beendet, sobald diese erkennt, dass die Task-ID aus der Auflistung der Lebensdauer entfernt wurde.

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

Prüfpunkt 4

An diesem Punkt können Sie die Komponente erstellen.

So testen Sie Ihre Komponente

  • Kompilieren Sie die Komponente.

Die PrimeNumberCalculator-Komponente ist jetzt vollständig und einsatzbereit.

Ein Beispiel für einen Client, der die PrimeNumberCalculator-Komponente verwendet, finden Sie unter Gewusst wie: Implementieren eines Clients des ereignisbasierten asynchronen Musters.

Nächste Schritte

Sie können dieses Beispiel ausfüllen, indem Sie CalculatePrime schreiben. Dies ist das synchrone Äquivalent der CalculatePrimeAsync-Methode. So wird die PrimeNumberCalculator-Komponente vollständig kompatibel mit dem ereignisbasierten asynchronen Muster.

Sie können dieses Beispiel verbessern, indem Sie die Liste aller Primzahlen beibehalten, die durch verschiedene Aufrufe für verschiedene Testzahlen entdeckt wurden. Mit diesem Ansatz profitiert jeder Task von der Arbeit früherer Tasks. Achten Sie darauf, diese Liste mit lock-Regionen zu schützen, damit der Zugriff auf die Liste durch verschiedene Threads serialisiert wird.

Außerdem können Sie dieses Beispiel auch verbessern, indem Sie auf triviale Divisoren wie 2, 3 und 5 testen.

Weitere Informationen