Instrukcje: Implementacja składnika obsługującego wzorzec asynchroniczny oparty na zdarzeniach

Jeśli piszesz klasę z niektórymi operacjami, które mogą powodować zauważalne opóźnienia, rozważ nadanie jej asynchronicznej funkcjonalności przez zaimplementowanie asynchronicznego przeglądu wzorca opartego na zdarzeniach.

W tym przewodniku pokazano, jak utworzyć składnik implementujący wzorzec asynchroniczny oparty na zdarzeniach. Jest on implementowany przy użyciu klas pomocników z System.ComponentModel przestrzeni nazw, co zapewnia, że składnik działa prawidłowo w ramach dowolnego modelu aplikacji, w tym ASP.NET, aplikacji konsolowych i aplikacji Windows Forms. Ten składnik można również zaprojektować za pomocą PropertyGrid kontrolki i własnych projektantów niestandardowych.

Po zakończeniu będziesz mieć aplikację, która asynchronicznie oblicza liczby pierwsze. Aplikacja będzie mieć główny wątek interfejsu użytkownika i wątek dla każdego obliczenia liczby głównej. Mimo że testowanie, czy duża liczba jest liczbą pierwszą, może zająć zauważalną ilość czasu, główny wątek interfejsu użytkownika nie zostanie przerwany przez to opóźnienie, a formularz będzie odpowiadać podczas obliczeń. Będzie można uruchamiać tyle obliczeń, ile chcesz współbieżnie i selektywnie anulować oczekujące obliczenia.

Zadania przedstawione w tym przewodniku obejmują:

  • Tworzenie składnika

  • Definiowanie publicznych zdarzeń asynchronicznych i delegatów

  • Definiowanie delegatów prywatnych

  • Implementowanie zdarzeń publicznych

  • Implementowanie metody uzupełniania

  • Implementowanie metod procesu roboczego

  • Implementowanie metod uruchamiania i anulowania

Aby skopiować kod w tym temacie jako pojedynczą listę, zobacz How to: Implement a Client of the Event-based Asynchronous Pattern (Instrukcje: implementowanie klienta wzorca asynchronicznego opartego na zdarzeniach).

Tworzenie składnika

Pierwszym krokiem jest utworzenie składnika, który będzie implementować wzorzec asynchroniczny oparty na zdarzeniach.

Aby utworzyć składnik

  • Utwórz klasę o nazwie PrimeNumberCalculator , która dziedziczy z klasy Component.

Definiowanie publicznych zdarzeń asynchronicznych i delegatów

Składnik komunikuje się z klientami przy użyciu zdarzeń. Zdarzenie MethodNameCompleted powiadamia klientów o ukończeniu zadania asynchronicznego, a zdarzenie MethodNameProgressChanged informuje klientów o postępie zadania asynchronicznego.

Aby zdefiniować zdarzenia asynchroniczne dla klientów składnika:

  1. Zaimportuj System.Threading przestrzenie nazw i System.Collections.Specialized w górnej części pliku.

    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. Przed definicją klasy zadeklaruj PrimeNumberCalculator delegatów dla zdarzeń postępu i ukończenia.

    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. W definicji klasy zadeklaruj PrimeNumberCalculator zdarzenia raportowania postępu i ukończenia dla klientów.

    public event ProgressChangedEventHandler ProgressChanged;
    public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
    
    Public Event ProgressChanged _
        As ProgressChangedEventHandler
    Public Event CalculatePrimeCompleted _
        As CalculatePrimeCompletedEventHandler
    
  4. PrimeNumberCalculator Po definicji klasy należy utworzyć klasę CalculatePrimeCompletedEventArgs do raportowania wyniku każdego obliczenia do programu obsługi zdarzeń klienta dla CalculatePrimeCompletedzdarzenia .. Oprócz AsyncCompletedEventArgs właściwości ta klasa umożliwia klientowi określenie, jaka liczba została przetestowana, czy jest liczbą pierwszą i jaka jest pierwsza dzielnik, jeśli nie jest liczbą pierwszą.

    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
    

Punkt kontrolny 1

W tym momencie możesz skompilować składnik.

Aby przetestować składnik

  • Skompiluj składnik.

    Zostaną wyświetlone dwa ostrzeżenia kompilatora:

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

    Te ostrzeżenia zostaną wyczyszczone w następnej sekcji.

Definiowanie delegatów prywatnych

Aspekty PrimeNumberCalculator asynchroniczne składnika są implementowane wewnętrznie za pomocą specjalnego delegata SendOrPostCallbackznanego jako . Obiekt SendOrPostCallback reprezentuje metodę wywołania zwrotnego wykonywaną w wątku ThreadPool . Metoda wywołania zwrotnego musi mieć podpis, który przyjmuje pojedynczy parametr typu Object, co oznacza, że musisz przekazać stan między delegatami w klasie otoki. Aby uzyskać więcej informacji, zobacz SendOrPostCallback.

Aby zaimplementować wewnętrzne zachowanie asynchroniczne składnika:

  1. Zadeklaruj SendOrPostCallbackPrimeNumberCalculator i utwórz delegatów w klasie . SendOrPostCallback Utwórz obiekty w metodzie narzędziowej o nazwie InitializeDelegates.

    Będziesz potrzebować dwóch delegatów: jeden do raportowania postępu do klienta, a drugi do raportowania ukończenia do klienta.

    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. Wywołaj metodę InitializeDelegates w konstruktorze składnika.

    public PrimeNumberCalculator()
    {
        InitializeComponent();
    
        InitializeDelegates();
    }
    
    Public Sub New()
    
        InitializeComponent()
    
        InitializeDelegates()
    
    End Sub
    
  3. Zadeklaruj PrimeNumberCalculator delegata w klasie, który obsługuje rzeczywistą pracę do wykonania asynchronicznie. Ten delegat opakowuje metodę procesu roboczego, która sprawdza, czy liczba jest liczbą pierwszą. Delegat przyjmuje AsyncOperation parametr , który będzie używany do śledzenia okresu istnienia operacji asynchronicznej.

    private delegate void WorkerEventHandler(
        int numberToCheck,
        AsyncOperation asyncOp);
    
    Private Delegate Sub WorkerEventHandler( _
    ByVal numberToCheck As Integer, _
    ByVal asyncOp As AsyncOperation)
    
  4. Utwórz kolekcję do zarządzania okresami istnienia oczekujących operacji asynchronicznych. Klient potrzebuje sposobu śledzenia operacji wykonywanych i zakończonych, a śledzenie jest wykonywane przez wymaganie od klienta przekazania unikatowego tokenu lub identyfikatora zadania, gdy klient wykonuje wywołanie metody asynchronicznej. Składnik PrimeNumberCalculator musi śledzić każde wywołanie przez skojarzenie identyfikatora zadania z odpowiadającym mu wywołaniem. Jeśli klient przekazuje identyfikator zadania, który nie jest unikatowy, PrimeNumberCalculator składnik musi zgłosić wyjątek.

    Składnik PrimeNumberCalculator śledzi identyfikator zadania przy użyciu specjalnej klasy kolekcji o nazwie HybridDictionary. W definicji klasy utwórz element o HybridDictionary nazwie userStateToLifetime.

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

Implementowanie zdarzeń publicznych

Składniki implementujące wzorzec asynchroniczny oparty na zdarzeniach komunikują się z klientami przy użyciu zdarzeń. Te zdarzenia są wywoływane we właściwym wątku za pomocą AsyncOperation klasy .

Aby zgłosić zdarzenia do klientów składnika:

  1. Zaimplementuj zdarzenia publiczne na potrzeby raportowania do klientów. Będzie potrzebne zdarzenie do raportowania postępu i jedno do ukończenia raportowania.

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

Implementowanie metody uzupełniania

Delegat ukończenia jest metodą, która jest wywoływana przez bazowe działanie asynchroniczne bezwątkowa, gdy operacja asynchroniczna kończy się powodzeniem, ukończeniem, błędem lub anulowaniem. To wywołanie odbywa się w dowolnym wątku.

Ta metoda polega na usunięciu identyfikatora zadania klienta z wewnętrznej kolekcji unikatowych tokenów klienta. Ta metoda kończy również okres istnienia określonej operacji asynchronicznej przez wywołanie PostOperationCompleted metody w odpowiedniej AsyncOperationmetodzie . To wywołanie wywołuje zdarzenie ukończenia w wątku, który jest odpowiedni dla modelu aplikacji. Po wywołaniu PostOperationCompleted metody nie można już używać tego wystąpienia AsyncOperation , a wszelkie kolejne próby jej użycia spowodują zgłoszenie wyjątku.

Podpis CompletionMethod musi przechowywać cały stan niezbędny do opisania wyniku operacji asynchronicznej. Zawiera stan liczby, która została przetestowana przez tę konkretną operację asynchroniczną, czy liczba jest liczbą pierwszą, oraz wartością pierwszego dzielnika, jeśli nie jest liczbą pierwszą. Zawiera również stan opisujący wszelkie wyjątki, które wystąpiły, oraz AsyncOperation odpowiadający temu konkretnemu zadaniu.

Aby ukończyć operację asynchroniczną:

  • Zaimplementuj metodę uzupełniania. Przyjmuje ona sześć parametrów, których używa do wypełnienia CalculatePrimeCompletedEventArgs elementu zwracanego do klienta za pośrednictwem klienta CalculatePrimeCompletedEventHandler. Usuwa token identyfikatora zadania klienta z kolekcji wewnętrznej i kończy okres istnienia operacji asynchronicznej wywołaniem metody PostOperationCompleted. Wywołuje AsyncOperation wywołanie wątku lub kontekstu, który jest odpowiedni dla modelu aplikacji.

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

Punkt kontrolny 2

W tym momencie możesz skompilować składnik.

Aby przetestować składnik

  • Skompiluj składnik.

    Zostanie wyświetlone jedno ostrzeżenie kompilatora:

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

    To ostrzeżenie zostanie rozwiązane w następnej sekcji.

Implementowanie metod procesu roboczego

Do tej pory zaimplementowano pomocniczy kod asynchroniczny dla PrimeNumberCalculator składnika. Teraz możesz zaimplementować kod, który wykonuje rzeczywistą pracę. Zaimplementujesz trzy metody: CalculateWorker, BuildPrimeNumberListi IsPrime. BuildPrimeNumberList Razem i IsPrime składają się z dobrze znanego algorytmu o nazwie Sieve of Eratosthenes, który określa, czy liczba jest liczbą pierwszą, znajdując wszystkie liczby pierwsze aż do pierwiastek kwadratowy liczby testowej. Jeśli w tym momencie nie znaleziono żadnych dzielników, liczba testowa jest liczbą pierwszorźą.

Gdyby ten składnik został napisany pod kątem maksymalnej wydajności, zapamiętałby wszystkie liczby pierwsze wykryte przez różne wywołania dla różnych liczb testowych. Sprawdzałby się również pod kątem trywialnych dzielników, takich jak 2, 3 i 5. Celem tego przykładu jest pokazanie, jak czasochłonne operacje mogą być wykonywane asynchronicznie, więc te optymalizacje są pozostawione jako ćwiczenie dla Ciebie.

Metoda CalculateWorker jest opakowana w delegata i jest wywoływana asynchronicznie za pomocą wywołania metody .BeginInvoke

Uwaga

Raportowanie postępu jest implementowane w metodzie BuildPrimeNumberList . Na szybkich komputerach ProgressChanged zdarzenia mogą być szybko wywoływane. Wątek klienta, na którym są zgłaszane te zdarzenia, musi być w stanie obsłużyć tę sytuację. Kod interfejsu użytkownika może być zalany komunikatami i nie może nadążyć, co powoduje brak odpowiedzi. Przykładowy interfejs użytkownika, który obsługuje tę sytuację, zobacz How to: Implement a Client of the Event-based Asynchronous Pattern (Instrukcje: implementowanie klienta wzorca asynchronicznego opartego na zdarzeniach).

Aby wykonać asynchronicznie obliczanie liczby prime number:

  1. Zaimplementuj metodę TaskCanceled narzędzia. Spowoduje to sprawdzenie kolekcji okresu istnienia zadania dla danego identyfikatora zadania i zwraca true wartość, jeśli identyfikator zadania nie zostanie znaleziony.

    // 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. Zaimplementuj metodę CalculateWorker . Przyjmuje dwa parametry: liczbę do przetestowania i .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. Zaimplementuj .BuildPrimeNumberList Przyjmuje dwa parametry: liczbę do przetestowania i .AsyncOperation Używa go do raportowania AsyncOperation postępu i wyników przyrostowych. Zapewnia to, że programy obsługi zdarzeń klienta są wywoływane we właściwym wątku lub kontekście modelu aplikacji. Po BuildPrimeNumberList znalezieniu liczby prime zgłasza ją jako przyrostowy wynik programu obsługi zdarzeń klienta dla ProgressChanged zdarzenia. Wymaga to klasy pochodnej z ProgressChangedEventArgsklasy o nazwie CalculatePrimeProgressChangedEventArgs, która ma jedną dodaną właściwość o nazwie LatestPrimeNumber.

    Metoda BuildPrimeNumberList okresowo wywołuje również metodę TaskCanceled i kończy działanie, jeśli metoda zwraca wartość 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. Zaimplementuj .IsPrime Przyjmuje trzy parametry: listę znanych liczb pierwszych, liczbę do przetestowania i parametr wyjściowy dla pierwszego znalezionego dzielnika. Biorąc pod uwagę listę liczb głównych, określa, czy liczba testowa jest liczbą pierwszą.

    // 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. Wyprowadzanie CalculatePrimeProgressChangedEventArgs z klasy ProgressChangedEventArgs. Ta klasa jest niezbędna do raportowania wyników przyrostowych do programu obsługi zdarzeń klienta dla zdarzenia ProgressChanged . Ma jedną dodaną właściwość o nazwie 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
    

Punkt kontrolny 3

W tym momencie możesz skompilować składnik.

Aby przetestować składnik

  • Skompiluj składnik.

    Wszystko, co pozostało do zapisania, to metody uruchamiania i anulowania operacji asynchronicznych oraz CalculatePrimeAsyncCancelAsync.

Implementowanie metod uruchamiania i anulowania

Metodę procesu roboczego należy uruchomić we własnym wątku, wywołując BeginInvoke delegata, który go opakowuje. Aby zarządzać okresem istnienia określonej operacji asynchronicznej, należy wywołać metodę CreateOperation w AsyncOperationManager klasie pomocnika. Spowoduje to zwrócenie elementu AsyncOperation, który marshalsuje wywołania procedur obsługi zdarzeń klienta do odpowiedniego wątku lub kontekstu.

Należy anulować określoną oczekującą operację, wywołując PostOperationCompleted odpowiednią operację AsyncOperation. Spowoduje to zakończenie tej operacji, a wszystkie kolejne wywołania do tej AsyncOperation operacji spowodują zgłoszenie wyjątku.

Aby zaimplementować funkcję Uruchom i Anuluj:

  1. Zaimplementuj metodę CalculatePrimeAsync . Upewnij się, że token dostarczony przez klienta (identyfikator zadania) jest unikatowy w odniesieniu do wszystkich tokenów reprezentujących aktualnie oczekujące zadania. Jeśli klient przekazuje token nieu unikatowego, CalculatePrimeAsync zgłasza wyjątek. W przeciwnym razie token zostanie dodany do kolekcji identyfikatorów zadań.

    // 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. Zaimplementuj metodę CancelAsync . taskId Jeśli parametr istnieje w kolekcji tokenów, zostanie usunięty. Zapobiega to anulowaniu zadań, które nie zostały uruchomione. Jeśli zadanie jest uruchomione, BuildPrimeNumberList metoda kończy działanie po wykryciu usunięcia identyfikatora zadania z kolekcji okresu istnienia.

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

Punkt kontrolny 4

W tym momencie możesz skompilować składnik.

Aby przetestować składnik

  • Skompiluj składnik.

Składnik PrimeNumberCalculator jest teraz gotowy do użycia.

Przykładowy klient używający PrimeNumberCalculator składnika można znaleźć w temacie How to: Implement a Client of the Event-based Asynchronous Pattern (Jak zaimplementować klienta wzorca asynchronicznego opartego na zdarzeniach).

Następne kroki

Możesz wypełnić ten przykład, pisząc CalculatePrimesynchroniczną odpowiednik CalculatePrimeAsync metody . Dzięki temu składnik będzie PrimeNumberCalculator w pełni zgodny ze wzorcem asynchronicznym opartym na zdarzeniach.

Ten przykład można poprawić, zachowując listę wszystkich liczb głównych odnalezionych przez różne wywołania dla różnych liczb testowych. Przy użyciu tego podejścia każde zadanie będzie korzystać z pracy wykonanej przez poprzednie zadania. Należy zachować ostrożność, aby chronić tę listę za pomocą lock regionów, więc dostęp do listy według różnych wątków jest serializowany.

Możesz również ulepszyć ten przykład, testując pod kątem trywialnych dzielników, takich jak 2, 3 i 5.

Zobacz też