Exemplarische Vorgehensweise: Implementieren einer Komponente, die das ereignisbasierte asynchrone Muster unterstützt

Wenn Sie eine Klasse mit Operationen schreiben, durch die nennenswerte Verzögerungen auftreten können, sollten Sie sie mit einer asynchronen Funktionalität ausstatten, indem Sie die unter Übersicht über ereignisbasierte asynchrone Muster beschriebenen Schritte implementieren.

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, was eine einwandfreie Funktionsweise der Komponente unter jedem beliebigen Anwendungsmodell, einschließlich ASP.NET, Konsolenanwendungen und Anwendungen von Windows Forms, gewährleistet. Diese Komponente ist auch mit einem PropertyGrid-Steuerelement und den eigenen benutzerdefinierten Designern ausführbar.

Nach dem Implementieren steht Ihnen eine Komponente zur Verfügung, die Primzahlen asynchron berechnet. Die Anwendung ist mit einem Haupthread der Benutzeroberfläche (UI) und einem Thread für jede einzelne Primzahlenberechnung ausgestattet. Obwohl die Primzahlenberechnung mitunter sehr zeitaufwändig ist, wird der Hauptthread der Benutzeroberfläche durch diese Verzögerung nicht unterbrochen. Die Reaktivität der Benutzeroberfläche bleibt erhalten. Sie können daher beliebig viele Berechnungen gleichzeitig ausführen und ausstehende Berechnungen wahlweise abbrechen.

Zu den Aufgaben in dieser exemplarischen Vorgehensweise gehören:

  • 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 der Start-Methode und der Cancel-Methode

Informationen zum Kopieren des in diesem Thema behandelten Codes als einzelne Auflistung finden Sie unter Gewusst wie: Implementieren einer Komponente, die das ereignisbasierte asynchrone Muster unterstützt.

Erstellen der Komponente

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

So erstellen Sie die Komponente

  • Erstellen Sie eine Klasse mit dem Namen PrimeNumberCalculator, die von Component erbt.

Definieren von öffentlichen, asynchronen Ereignissen und Delegaten

Die Komponente kommuniziert mit den Clients mithilfe von Ereignissen. Die Clients erhalten vom MethodNameCompleted-Ereignis eine Warnung, dass eine asynchrone Aufgabe abgeschlossen ist. Vom MethodNameProgressChanged-Ereignis werden die Clients über den Status einer asynchronen Aufgabe informiert.

So definieren Sie asynchrone Ereignisse für Clients der Komponente:

  1. Importieren Sie den System.Threading-Namespace und den System.Collections.Specialized-Namespace am Anfang der Datei.

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

    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. Deklarieren Sie während der Definition der PrimeNumberCalculator -Klasse Ereignisse, die an die Clients Status und Abschluss melden.

    Public Event ProgressChanged _
        As ProgressChangedEventHandler
    Public Event CalculatePrimeCompleted _
        As CalculatePrimeCompletedEventHandler
    
    public event ProgressChangedEventHandler ProgressChanged;
    public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
    
  4. Leiten Sie nach der Definition der PrimeNumberCalculator -Klasse die CalculatePrimeCompletedEventArgs -Klasse ab, die den Ereignishandler des Clients für das CalculatePrimeCompleted-Ereignis über das Ergebnis jeder Berechnung informiert. Neben den AsyncCompletedEventArgs-Eigenschaften kann der Client mittels dieser Klasse feststellen, welche Zahl getestet wurde, ob es sich um eine Primzahl handelt und, falls keine Primzahl vorliegt, wie der erste Divisor lautet.

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

Checkpoint

An diesem Punkt können Sie die Komponente erstellen.

So testen Sie die Komponente

  • Kompilieren Sie die Komponente.

    Sie empfangen daraufhin zwei Compiler-Warnungen:

    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 PrimeNumberCalculator -Komponente werden intern mit einem gesonderten, als SendOrPostCallback bekannten Delegaten implementiert. Ein SendOrPostCallback stellt eine Rückrufmethode dar, die in einem ThreadPool-Thread ausgeführt wird. Die Rückrufmethode muss über eine Signatur verfügen, die einen einzelnen Parameter des Typs Object übernimmt. Dies bedeutet, dass Sie an Delegaten in einer Wrapperklasse Zustand übergeben müssen. Weitere Informationen finden Sie unter SendOrPostCallback.

So implementieren Sie das interne asynchrone Verhalten der Komponente:

  1. Deklarieren und erzeugen Sie die SendOrPostCallback-Delegaten in der PrimeNumberCalculator -Klasse. Erstellen Sie die SendOrPostCallback-Objekte in einer Dienstprogrammmethode mit dem Namen InitializeDelegates.

    Sie benötigen zwei Delegate: einen für den Statusbericht an den Client, den anderen für die Abschlussmeldung an den 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. Rufen Sie die InitializeDelegates-Methode im Konstruktor der Komponente auf.

    Public Sub New()
    
        InitializeComponent()
    
        InitializeDelegates()
    
    End Sub
    
    public PrimeNumberCalculator()
    {   
        InitializeComponent();
    
        InitializeDelegates();
    }
    
  3. Deklarieren Sie einen Delegaten in der PrimeNumberCalculator-Klasse, der die eigentliche, asynchron zu verrichtende Arbeit erledigt. Dieser Delegat umschließt die Workermethode, die eine Zahl auf ihre Primzahleigenschaft testet. Der Delegat übernimmt einen AsyncOperation-Parameter, mit dem dann die Lebensdauer der asynchronen Operation verfolgt wird.

    Private Delegate Sub WorkerEventHandler( _
    ByVal numberToCheck As Integer, _
    ByVal asyncOp As AsyncOperation)
    
    private delegate void WorkerEventHandler(
        int numberToCheck,
        AsyncOperation asyncOp);
    
  4. Erstellen Sie zum Verwalten der Lebensdauer ausstehender, asynchroner Operationen eine Auflistung. Der Client muss auf eine bestimmte Art und Weise die Operationen bei Ausführung und Abschluss verfolgen. Dieses Verfolgen geschieht so, dass der Client beim Aufrufen der asynchronen Methode ein eindeutiges Token bzw. eine eindeutige Aufgaben-ID übergibt. Jeder Aufruf muss von der PrimeNumberCalculator-Komponente durch Zuordnen der Aufgaben-ID zum entsprechenden Aufruf verfolgt werden. Wenn der Client eine nicht eindeutige Aufgaben-ID übergibt, muss die PrimeNumberCalculator-Komponente eine Ausnahme auslösen.

    Die PrimeNumberCalculator-Komponente verfolgt die Aufgaben-ID mithilfe einer gesonderte Auflistungsklasse, die HybridDictionary genannt wird. Erstellen Sie beim Definieren der Klasse eine HybridDictionary mit dem Namen userTokenToLifetime.

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

Implementieren von öffentlichen Ereignissen

Komponenten, die das ereignisbasierte asynchrone Muster implementieren, kommunizieren mit den Clients über Ereignisse. Diese Ereignisse werden im entsprechenden Thread mithilfe der AsyncOperation-Klasse aufgerufen.

So lösen Sie Ereignisse zu den Clients der Komponente aus:

  • Implementieren Sie öffentliche Ereignisse für die Benachrichtigung von Clients. Sie benötigen ein Ereignis für die Statusmeldung und ein Ereignis für die Abschlussmeldung.

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

Implementieren der Abschlussmethode

Der Abschlussdelegat ist die Methode, die vom zugrunde liegenden asynchronen Verhalten mit freiem Thread dann aufgerufen wird, wenn die asynchrone Operation aufgrund eines erfolgreichen Abschlusses, eines Fehlers oder aber eines Abbruchs beendet wird. Ein solcher Aufruf wird in einem beliebigen Thread vollzogen.

Die Methode dient dazu, die Aufgaben-ID aus der internen Auflistung der eindeutigen Clienttokens zu entfernen. Die Methode beendet auch die Lebensdauer einer einzelnen asynchronen Operation durch Aufrufen der PostOperationCompleted-Methode für die entsprechende AsyncOperation. Dieser Aufruf löst das Abschlussereignis in dem Thread aus, der für das Anwendungsmodell geeignet ist. Nach dem Aufrufen der PostOperationCompleted-Methode kann diese Instanz der AsyncOperation nicht länger verwendet werden, sodass nachfolgende Verwendungsversuche eine Ausnahme auslösen.

Die CompletionMethod-Signatur muss alle Zustände enthalten, die notwendig sind, um das Ergebnis der asynchronen Operation zu beschreiben. Diese Klasse speichert den Zustand für die Zahl, die von dieser asynchronen Operation getestet wurde, und sie gibt an, ob die Zahl eine Primzahl ist. Wenn es sich nicht um eine Primzahl handelt, gibt die Klasse den ersten Divisor an. Zusätzlich speichert die Klasse den Zustand jeder aufgetretenen Ausnahme sowie die AsyncOperation, die dieser Aufgabe entspricht.

So schließen Sie eine asynchrone Operation ab:

  • Implementieren Sie die Abschlussmethode. Sie nimmt sechs Parameter an, mit denen sie ein CalculatePrimeCompletedEventArgs auffüllt, das durch den CalculatePrimeCompletedEventHandler des Clients an den Client zurückgegeben wird. Die Methode entfernt das Token bzw. die Aufgaben-ID des Clients aus der internen Auflistung und beendet die Lebensdauer der asynchronen Methode durch Aufrufen von PostOperationCompleted. Die AsyncOperation führt das Marshalling des Aufrufs an den auf das Anwendungsmodell passenden Thread oder Kontext aus.

    ' 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.
    }
    

Checkpoint

An diesem Punkt können Sie die Komponente erstellen.

So testen Sie die Komponente

  • Kompilieren Sie die Komponente.

    Sie empfangen 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

Sie haben bereits den unterstützenden asynchronen Code für die PrimeNumberCalculator-Komponente implementiert. Jetzt können Sie den Code für die eigentliche Arbeit implementieren. Sie implementieren drei Methoden: CalculateWorker, BuildPrimeNumberList und IsPrime. BuildPrimeNumberList und IsPrime bilden zusammen einen bekannten Algorithmus, das Sieb des Eratosthenes, mit dem eine Zahl auf ihre Primzahleigenschaft hin untersucht wird, indem aus einem bestimmten Zahlenbereich alle Primzahlen bis zur Quadratwurzel der untersuchten Zahl ermittelt werden. Wenn bis zu diesem Punkt keine Primteiler gefunden werden, dann handelt es sich bei der getesteten Zahl um eine Primzahl.

Falls diese Komponente geschrieben wäre, um größtmögliche Effizienz zu erzielen, würden alle von verschiedenen Aufrufen für viele verschiedene Testzahlen ermittelten Primzahlen abrufbar sein. Die Komponente würde auch auf einfache Primteiler wie 2, 3 und 5 prüfen. Ziel dieses Beispieles ist es zu zeigen, wie zeitaufwändig asynchron ausgeführte Operationen sein können. Daher sollen Ihnen diese Optimierungen als Übung dienen.

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

HinweisHinweis

Die Fortschrittsberichterstellung wird in der BuildPrimeNumberList-Methode implementiert.Auf schnellen Computern können ProgressChanged-Ereignisse in schneller Folge ausgelöst werden.Der Clientthread, anhand dessen diese Ereignisse ausgelöst werden, muss mit dieser Situation umgehen können.Der Code für die Benutzeroberfläche wird möglicherweise mit Meldungen überschwemmt und kann nicht darauf reagieren, was zu einem absturzähnlichen Verhalten führt.Eine Beispielbenutzeroberfläche, die mit dieser Situation zurechtkommt, finden Sie unter Gewusst wie: Implementieren eines Clients des ereignisbasierten asynchronen Musters.

So führen Sie die Primzahlenberechnung asynchron aus:

  1. Implementieren Sie die TaskCanceled-Dienstprogrammmethode. Diese überprüft die Auflistung der Aufgabenlebensdauer für die angegebene Aufgaben-ID und gibt true zurück, wenn die Aufgaben-ID nicht gefunden wurde.

    ' 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. Implementieren Sie die CalculateWorker-Methode. Es werden zwei Parameter angenommen: eine Testzahl und eine 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. Sie können BuildPrimeNumberList implementieren. Es werden zwei Parameter übernommen: die Testzahl und eine AsyncOperation. Es wird die AsyncOperation verwendet, um Status und inkrementelle Ergebnisse zu melden. Damit wird sichergestellt, dass die Ereignishandler des Clients in dem auf das Anwendungsmodell passenden Thread oder Kontext aufgerufen werden. Wenn BuildPrimeNumberList eine Primzahl ermittelt, wird dies in Form eines inkrementellen Ergebnisses an den für das ProgressChanged-Ereignis bestimmten Ereignishandler des Clients gemeldet. Dies erfordert eine von ProgressChangedEventArgs abgeleitete Klasse namens CalculatePrimeProgressChangedEventArgs, die über eine hinzugefügte Eigenschaft mit dem Namen LatestPrimeNumber verfügt.

    Die BuildPrimeNumberList-Methode ruft auch periodisch die TaskCanceled-Methode auf und endet, wenn die Methode true zurückgibt.

    ' 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. Sie können IsPrime implementieren. Übernommen werden drei Parameter: eine Liste bekannter Primzahlen, die Testzahl und ein Ausgabeparameter für den ersten gefundenen Divisor. Anhand der Liste mit den Primzahlen wird die Testzahl auf ihre Primzahleigenschaft hin überprüft.

    ' 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. Leiten Sie CalculatePrimeProgressChangedEventArgs von ProgressChangedEventArgs ab. Diese Klasse ist für das Melden von inkrementellen Ergebnissen an die für das ProgressChanged-Ereignis vorgesehenen Ereignishandler des Clients erforderlich. Die Klasse verfügt über eine zusätzliche Eigenschaft mit dem Namen 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;
            }
        }
    }
    

Checkpoint

An diesem Punkt können Sie die Komponente erstellen.

So testen Sie die Komponente

  • Kompilieren Sie die Komponente.

    Nun sind lediglich noch die Methoden zum Starten und Abbrechen von asynchronen Operationen zu schreiben, und zwar die CalculatePrimeAsync-Methode und die CancelAsync-Methode.

Implementieren der Start-Methode und der Cancel-Methode

Sie starten die Workermethode in deren Thread durch Aufrufen von BeginInvoke für den Delegaten, mit dem diese Methode umschlossen ist. Zum Verwalten der Lebensdauer einer bestimmten, asynchronen Operation rufen Sie die CreateOperation-Methode für die AsyncOperationManager-Hilfsklasse auf. Daraufhin wird eine AsyncOperation zurückgegeben, die das Marshalling von Aufrufen von Ereignishandlern des Clients an den entsprechenden Thread oder Kontext vornimmt.

Sie brechen eine einzelne ausstehende Operation so ab, indem Sie PostOperationCompleted für die entsprechende AsyncOperation aufrufen. Damit wird diese Operation beendet, und alle nachfolgenden Aufrufe der AsyncOperation lösen eine Ausnahme aus.

So implementieren Sie die Start-Funktion und die Cancel-Funktion:

  1. Implementieren Sie die CalculatePrimeAsync-Methode. Stellen Sie sicher, dass das Token bzw. die Aufgaben-ID eindeutig ist, und zwar im Hinblick auf alle Tokens der aktuell ausstehenden Aufgaben. Wenn der Client ein nicht eindeutiges Token übergibt, dann wird von CalculatePrimeAsync eine Ausnahme ausgelöst. Andernfalls wird das Token der Aufgaben-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 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. Implementieren Sie die CancelAsync-Methode. Wenn der taskId-Parameter in der Token-Auflistung vorhanden ist, wird er entfernt. Dadurch werden abgebrochene Aufgaben, die nicht gestartet wurden, nicht ausgeführt. Wenn die Aufgabe ausgeführt wird, wird die BuildPrimeNumberList-Methode beendet, wenn sie feststellt, dass die Aufgaben-ID aus der Auflistung der Lebensdauer entfernt wurde.

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

Checkpoint

An diesem Punkt können Sie die Komponente erstellen.

So testen Sie die Komponente

  • Kompilieren Sie die Komponente.

Die PrimeNumberCalculator -Komponente ist nun 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, das synchrone Äquivalent der CalculatePrimeAsync-Methode, schreiben. Hiermit erreichen Sie, dass die PrimeNumberCalculator-Komponente für das ereignisbasierte asynchrone Muster uneingeschränkt tauglich ist.

Sie können das Beispiel noch verbessern, indem Sie die Liste der aus der Menge der verschiedenen Testzahlen per Aufruf ermittelten Primzahlen speichern. Mit dieser Vorgehensweise kann jede Aufgabe von zuvor ausgeführten Aufgaben profitieren. Schützen Sie die Liste mit lock-Bereichen, damit der Zugriff auf die Liste durch verschiedene Threads serialisiert wird.

Sie können das Beispiel auch verbessern, indem Sie auf einfache Primteiler wie 2, 3 und 5 testen lassen.

Siehe auch

Aufgaben

Gewusst wie: Ausführen eines Vorgangs im Hintergrund

Gewusst wie: Implementieren einer Komponente, die das ereignisbasierte asynchrone Muster unterstützt

Konzepte

Übersicht über ereignisbasierte asynchrone Muster

Weitere Ressourcen

Multithreading in Visual Basic

Multithreadprogrammierung mit dem ereignisbasierten asynchronen Muster