Postupy: Implementace komponenty, která podporuje asynchronní vzor založený na událostech

Pokud píšete třídu s některými operacemi, které mohou způsobovat znatelné zpoždění, zvažte možnost poskytnout jí asynchronní funkce implementací přehledu asynchronního vzoru založeného na událostech.

Tento návod ukazuje, jak vytvořit komponentu, která implementuje asynchronní vzor založený na událostech. Je implementován pomocí pomocných tříd z System.ComponentModel oboru názvů, což zajišťuje, že komponenta funguje správně v jakémkoli aplikačním modelu, včetně ASP.NET, konzolových aplikací a model Windows Forms aplikací. Tato komponenta je také navržená s ovládacím prvkem PropertyGrid a vlastními návrháři.

Po dokončení budete mít aplikaci, která asynchronně počítá prvočísla. Vaše aplikace bude mít hlavní vlákno uživatelského rozhraní (UI) a vlákno pro každý výpočet prvočísla. I když testování, zda je velké číslo prvočíslo může trvat znatelnou dobu, hlavní vlákno uživatelského rozhraní nebude přerušeno tímto zpožděním a formulář bude reagovat během výpočtů. Budete moct souběžně spustit tolik výpočtů, kolik chcete, a selektivně zrušit čekající výpočty.

Mezi úlohy znázorněné v tomto návodu patří:

  • Vytvoření komponenty

  • Definování veřejných asynchronních událostí a delegátů

  • Definování soukromých delegátů

  • Implementace veřejných událostí

  • Implementace metody dokončení

  • Implementace metod pracovních procesů

  • Implementace metod Start a Cancel

Pokud chcete kód v tomto tématu zkopírovat jako jeden výpis, přečtěte si téma Postupy: Implementace klienta asynchronního vzoru založeného na událostech.

Vytvoření komponenty

Prvním krokem je vytvoření komponenty, která implementuje asynchronní vzor založený na událostech.

Vytvoření komponenty

  • Vytvořte třídu s názvem PrimeNumberCalculator , která dědí z Component.

Definování veřejných asynchronních událostí a delegátů

Vaše komponenta komunikuje s klienty pomocí událostí. MethodNameCompleted Událost upozorní klienty na dokončení asynchronní úlohy a MethodNameProgressChanged událost informuje klienty o průběhu asynchronní úlohy.

Definování asynchronních událostí pro klienty vaší komponenty:

  1. Naimportujte System.Threading obory názvů a System.Collections.Specialized v horní části souboru.

    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. Před definicí PrimeNumberCalculator třídy deklarujte delegáty pro události průběhu a dokončení.

    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. V definici PrimeNumberCalculator třídy deklarujte události pro hlášení průběhu a dokončení klientům.

    public event ProgressChangedEventHandler ProgressChanged;
    public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
    
    Public Event ProgressChanged _
        As ProgressChangedEventHandler
    Public Event CalculatePrimeCompleted _
        As CalculatePrimeCompletedEventHandler
    
  4. PrimeNumberCalculator Po definici třídy odvozujte CalculatePrimeCompletedEventArgs třídu pro hlášení výsledku každého výpočtu obslužné rutině události klienta pro CalculatePrimeCompleted.event. Kromě AsyncCompletedEventArgs vlastností umožňuje tato třída klientovi určit, jaké číslo bylo testováno, zda je prvočíslo a co je první dělitel, pokud není 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
    

Kontrolní bod 1

V tomto okamžiku můžete komponentu sestavit.

Testování komponenty

  • Zkompilujte komponentu.

    Zobrazí se dvě upozornění kompilátoru:

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

    Tato upozornění budou vymazána v další části.

Definování soukromých delegátů

Asynchronní aspekty PrimeNumberCalculator komponenty se implementují interně pomocí speciálního delegáta označovaného SendOrPostCallbackjako . Představuje SendOrPostCallback metodu zpětného volání, která se spouští ve vlákně ThreadPool . Metoda zpětného volání musí mít podpis, který přijímá jeden parametr typu Object, což znamená, že budete muset předávat stav mezi delegáty ve třídě obálky. Další informace naleznete v tématu SendOrPostCallback.

Implementace interního asynchronního chování komponenty:

  1. Deklarujte SendOrPostCallback a vytvořte delegáty ve PrimeNumberCalculator třídě . Objekty vytvořte SendOrPostCallback v obslužné metodě s názvem InitializeDelegates.

    Budete potřebovat dva delegáty: jednoho pro hlášení průběhu klientovi a jednoho pro hlášení dokončení klientovi.

    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. Zavolejte metodu InitializeDelegates v konstruktoru komponenty.

    public PrimeNumberCalculator()
    {
        InitializeComponent();
    
        InitializeDelegates();
    }
    
    Public Sub New()
    
        InitializeComponent()
    
        InitializeDelegates()
    
    End Sub
    
  3. Deklarujte delegáta PrimeNumberCalculator ve třídě, která zpracovává skutečnou práci, která má být provedena asynchronně. Tento delegát zabalí metodu pracovního procesu, která testuje, zda je číslo prvočíslo. Delegát přebírá AsyncOperation parametr, který se použije ke sledování životnosti asynchronní operace.

    private delegate void WorkerEventHandler(
        int numberToCheck,
        AsyncOperation asyncOp);
    
    Private Delegate Sub WorkerEventHandler( _
    ByVal numberToCheck As Integer, _
    ByVal asyncOp As AsyncOperation)
    
  4. Vytvořte kolekci pro správu životností čekajících asynchronních operací. Klient potřebuje způsob, jak sledovat operace, jak jsou prováděny a dokončeny, a toto sledování se provádí tak, že vyžaduje, aby klient předal jedinečný token nebo ID úlohy, když klient provádí volání asynchronní metody. Komponenta PrimeNumberCalculator musí sledovat každé volání přidružením ID úlohy k odpovídajícímu vyvolání. Pokud klient předá ID úlohy, které není jedinečné, musí komponenta PrimeNumberCalculator vyvolat výjimku.

    Komponenta PrimeNumberCalculator sleduje ID úlohy pomocí speciální třídy kolekce s názvem HybridDictionary. V definici třídy vytvořte objekt s HybridDictionary názvem userStateToLifetime.

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

Implementace veřejných událostí

Komponenty, které implementují asynchronní vzor založený na událostech, komunikují s klienty pomocí událostí. Tyto události jsou vyvolány ve správném vlákně pomocí AsyncOperation třídy .

Vyvolání událostí pro klienty vaší komponenty:

  1. Implementujte veřejné události pro generování sestav klientům. Událost budete potřebovat pro hlášení průběhu a jednu událost pro hlášení dokončení.

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

Implementace metody dokončení

Delegát dokončení je metoda, kterou základní asynchronní chování s volnými vlákny vyvolá, když asynchronní operace skončí úspěšným dokončením, chybou nebo zrušením. K tomuto vyvolání dochází u libovolného vlákna.

Tato metoda slouží k odebrání ID úlohy klienta z interní kolekce jedinečných tokenů klienta. Tato metoda také ukončí životnost konkrétní asynchronní operace voláním PostOperationCompleted metody v odpovídající AsyncOperation. Toto volání vyvolá událost dokončení ve vlákně, které je vhodné pro model aplikace. PostOperationCompleted Po volání metody již nelze tuto instanci AsyncOperation použít a všechny následné pokusy o použití této metody vyvolají výjimku.

Podpis CompletionMethod musí obsahovat všechny stavy potřebné k popisu výsledku asynchronní operace. Obsahuje stav čísla, které bylo testováno touto konkrétní asynchronní operací, zda je číslo prvočíslo, a hodnotu jeho prvního dělitele, pokud se nejedná o prvočíslo. Obsahuje také stav popisující všechny výjimky, ke kterým došlo, a AsyncOperation odpovídající této konkrétní úloze.

Dokončení asynchronní operace:

  • Implementujte metodu dokončení. Má šest parametrů, které používá k naplnění objektu CalculatePrimeCompletedEventArgs , který se vrátí klientovi prostřednictvím CalculatePrimeCompletedEventHandlerpříkazu . Odebere token ID úlohy klienta z interní kolekce a ukončí životnost asynchronní operace voláním PostOperationCompleted. Zařadí AsyncOperation volání vlákna nebo kontextu, který je vhodný pro aplikační model.

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

Kontrolní bod 2

V tomto okamžiku můžete komponentu sestavit.

Testování komponenty

  • Zkompilujte komponentu.

    Zobrazí se jedno upozornění kompilátoru:

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

    Toto upozornění bude vyřešeno v další části.

Implementace metod pracovních procesů

Zatím jste implementovali podpůrný asynchronní kód pro komponentu PrimeNumberCalculator . Teď můžete implementovat kód, který provádí skutečnou práci. Implementujete tři metody: CalculateWorker, BuildPrimeNumberLista IsPrime. BuildPrimeNumberList Společně a IsPrime tvoří známý algoritmus nazvaný Síto Eratosthenes, který určuje, zda je číslo prvočíslo, vyhledáním všech prvočísel až po druhou odmocninu testovacího čísla. Pokud v tomto okamžiku nejsou nalezeny žádné dělitele, je testovací číslo prvočíslo.

Pokud by tato komponenta byla napsána pro maximální efektivitu, zapamatovala by si všechna prvočísla zjištěná různými voláními pro různá testovací čísla. Kontroluje také triviální dělitele, jako jsou 2, 3 a 5. Záměrem tohoto příkladu je ukázat, jak se časově náročné operace dají provádět asynchronně, takže tyto optimalizace zůstanou jako cvičení pro vás.

Metoda CalculateWorker je zabalena v delegátu a je vyvolána asynchronně s voláním BeginInvoke.

Poznámka

V metodě se implementuje BuildPrimeNumberList vykazování průběhu. Na rychlých počítačích ProgressChanged je možné události generovat v rychlém sledu. Vlákno klienta, na kterém jsou tyto události vyvolány, musí být schopné tuto situaci zvládnout. Kód uživatelského rozhraní může být zahlcený zprávami a nemůže držet krok, což vede k nereagující. Příklad uživatelského rozhraní, které řeší tuto situaci, viz Postupy: Implementace klienta asynchronního vzoru založeného na událostech.

Asynchronní spuštění výpočtu prvočísla:

  1. Implementujte metodu TaskCanceled nástroje . Tím se v kolekci doby života úkolů zkontroluje dané ID úkolu, a pokud se ID úkolu nenajde, vrátí true se.

    // 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. Implementujte metodu CalculateWorker . Používá dva parametry: číslo k testování a .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. Implementujte BuildPrimeNumberList. Používá dva parametry: číslo, které se má testovat, a .AsyncOperation Používá k AsyncOperation hlášení průběhu a přírůstkových výsledků. Tím se zajistí, že se obslužné rutiny událostí klienta volají ve správném vlákně nebo kontextu pro aplikační model. Když BuildPrimeNumberList najde prvočíslo, oznámí ho jako přírůstkový výsledek obslužné rutině události klienta pro danou ProgressChanged událost. To vyžaduje třídu odvozenou z ProgressChangedEventArgstřídy s názvem CalculatePrimeProgressChangedEventArgs, která má jednu přidanou vlastnost s názvem LatestPrimeNumber.

    Metoda BuildPrimeNumberList také pravidelně volá metodu TaskCanceled a ukončí se, pokud metoda vrátí 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. Implementujte IsPrime. Přijímá tři parametry: seznam známých prvočísel, číslo, které se má testovat, a výstupní parametr prvního nalezeného dělitele. Vzhledem k seznamu prvočísel určuje, jestli je zkušební číslo prvočíslo prvočíslo.

    // 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. Odvozeno CalculatePrimeProgressChangedEventArgs z ProgressChangedEventArgs. Tato třída je nezbytná pro hlášení přírůstkových výsledků obslužné rutině události klienta pro danou ProgressChanged událost. Má jednu přidanou vlastnost s názvem 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
    

Kontrolní bod 3

V tomto okamžiku můžete komponentu sestavit.

Testování komponenty

  • Zkompilujte komponentu.

    Jediné, co zbývá zapsat, jsou metody pro spuštění a zrušení asynchronních operací CalculatePrimeAsync a CancelAsync.

Implementace metod Start a Cancel

Spustíte metodu pracovního procesu na vlastním vlákně voláním BeginInvoke delegáta, který ji zabalí. Chcete-li spravovat dobu života konkrétní asynchronní operace, zavoláte metodu CreateOperation na AsyncOperationManager pomocné třídě. Tím se vrátí , AsyncOperationkterý zařazuje volání obslužných rutin událostí klienta do správného vlákna nebo kontextu.

Konkrétní čekající operaci zrušíte voláním PostOperationCompleted odpovídající AsyncOperationoperace . Tím se tato operace ukončí a všechna následná volání AsyncOperation této operace vyvolají výjimku.

Implementace funkcí Start a Cancel:

  1. Implementujte metodu CalculatePrimeAsync . Ujistěte se, že token zadaný klientem (ID úlohy) je jedinečný s ohledem na všechny tokeny představující aktuálně čekající úlohy. Pokud klient předá token, který není jedinečný, CalculatePrimeAsync vyvolá výjimku. V opačném případě se token přidá do kolekce ID úlohy.

    // 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. Implementujte metodu CancelAsync . taskId Pokud parametr v kolekci tokenů existuje, odebere se. Tím zabráníte spuštění zrušených úloh, které nebyly spuštěny. Pokud je úloha spuštěná, metoda se ukončí, BuildPrimeNumberList když zjistí, že ID úlohy bylo odebráno z kolekce životnosti.

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

Kontrolní bod 4

V tomto okamžiku můžete komponentu sestavit.

Testování komponenty

  • Zkompilujte komponentu.

Komponenta PrimeNumberCalculator je teď dokončená a připravená k použití.

Příklad klienta, který používá komponentu PrimeNumberCalculator , najdete v tématu Postupy: Implementace klienta asynchronního vzoru založeného na událostech.

Další kroky

Tento příklad můžete vyplnit zápisem CalculatePrime, což je synchronní ekvivalent CalculatePrimeAsync metody. Díky tomu bude komponenta PrimeNumberCalculator plně kompatibilní s asynchronním vzorem založeným na událostech.

Tento příklad můžete vylepšit zachováním seznamu všech prvočísek zjištěných různými voláními různých testovacích čísel. Díky tomuto přístupu bude každý úkol těžit z práce provedené předchozími úkoly. Buďte opatrní při ochraně tohoto seznamu s oblastmi lock , takže přístup k seznamu podle různých vláken je serializován.

Tento příklad můžete také vylepšit testováním triviálních dělitelů, jako jsou 2, 3 a 5.

Viz také