Bagikan melalui


Panduan: Menerapkan Komponen yang Mendukung Pola Asinkron Berbasis Kejadian

Jika Anda menulis kelas dengan beberapa operasi yang mungkin menimbulkan penundaan yang nyata, pertimbangkan untuk memberikan fungsionalitas asinkron dengan menerapkan Gambaran Umum Pola Asinkron Berbasis Peristiwa.

Panduan ini menggambarkan cara membuat komponen yang menerapkan Pola Asinkron berbasis Peristiwa. Ini diimplementasikan menggunakan kelas pembantu dari namespace layanan System.ComponentModel, yang memastikan bahwa komponen berfungsi dengan benar di bawah model aplikasi apa pun, termasuk ASP.NET, aplikasi Konsol, dan aplikasi Formulir Windows. Komponen ini juga dapat didesain dengan kontrol PropertyGrid dan perancang kustom Anda sendiri.

Ketika Anda selesai, Anda akan memiliki aplikasi yang menghitung bilangan prima secara asinkron. Aplikasi Anda akan memiliki utas antarmuka pengguna (UI) utama dan utas untuk setiap perhitungan bilangan prima. Meskipun menguji apakah suatu bilangan besar adalah bilangan prima dapat memakan waktu lama, utas UI utama tidak akan terganggu oleh penundaan ini, dan formulir akan responsif selama perhitungan. Anda akan dapat menjalankan perhitungan sebanyak yang Anda suka secara bersamaan dan secara selektif membatalkan perhitungan yang tertunda.

Tugas yang diilustrasikan dalam panduan ini meliputi:

  • Membuat Komponen

  • Mendefinisikan Peristiwa dan Delegasi Asinkron Publik

  • Mendefinisikan Delegasi Privat

  • Mengimplementasikan Peristiwa Publik

  • Mengimplementasikan Metode Penyelesaian

  • Mengimplementasikan Metode Pekerja

  • Mengimplementasikan Metode Mulai dan Batal

Untuk menyalin kode dalam topik ini sebagai daftar tunggal, lihat Cara: Mengimplementasikan Klien Pola Asinkron Berbasis Peristiwa.

Membuat Komponen

Langkah pertama adalah membuat komponen yang akan menerapkan Pola Asinkron berbasis Peristiwa.

Untuk membuat komponen

  • Buat kelas bernama PrimeNumberCalculator yang mewarisi dari Component.

Mendefinisikan Peristiwa dan Delegasi Asinkron Publik

Komponen Anda berkomunikasi dengan klien menggunakan peristiwa. Peristiwa MethodNameCompleted memperingatkan klien atas penyelesaian tugas asinkron, dan peristiwa MethodNameProgressChanged menginformasikan klien atas progres sebuah tugas asinkron.

Untuk menentukan peristiwa asinkron untuk klien komponen Anda:

  1. Impor namespace layanan System.Threading dan System.Collections.Specialized di bagian atas file Anda.

    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. Sebelum definisi kelas PrimeNumberCalculator, deklarasikan delegasi untuk peristiwa kemajuan dan penyelesaian.

    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. Dalam definisi kelas PrimeNumberCalculator, deklarasikan peristiwa untuk melaporkan kemajuan dan penyelesaian kepada klien.

    public event ProgressChangedEventHandler ProgressChanged;
    public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
    
    Public Event ProgressChanged _
        As ProgressChangedEventHandler
    Public Event CalculatePrimeCompleted _
        As CalculatePrimeCompletedEventHandler
    
  4. Setelah definisi kelas PrimeNumberCalculator, dapatkan kelas CalculatePrimeCompletedEventArgs untuk melaporkan hasil setiap perhitungan ke penanganan aktivitas klien untuk peristiwa CalculatePrimeCompleted. Selain properti AsyncCompletedEventArgs, kelas ini memungkinkan klien untuk menentukan nomor apa yang diuji, apakah itu prima, dan apa pembagi pertamanya jika bukan prima.

    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
    

Titik pemeriksaan 1

Pada titik ini, Anda dapat membangun komponen.

Untuk menguji komponen Anda

  • Kompilasi komponen.

    Anda akan menerima dua peringatan pengompilasi:

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

    Peringatan ini akan dihapus di bagian berikutnya.

Mendefinisikan Delegasi Privat

Aspek asinkron komponen PrimeNumberCalculator diimplementasikan secara internal dengan delegasi khusus yang dikenal sebagai SendOrPostCallback. SendOrPostCallback mewakili metode panggilan balik yang dijalankan pada utas ThreadPool. Metode panggilan balik harus memiliki tanda tangan yang mengambil satu parameter jenis Object, yang berarti Anda harus meneruskan status di antara delegasi dalam kelas pembungkus. Untuk informasi selengkapnya, lihat SendOrPostCallback.

Untuk menerapkan perilaku asinkron internal komponen Anda:

  1. Deklarasikan dan buat delegasi SendOrPostCallback di kelas PrimeNumberCalculator. Buat objek SendOrPostCallback dalam metode utilitas yang disebut InitializeDelegates.

    Anda akan membutuhkan dua delegasi: satu untuk melaporkan kemajuan kepada klien, dan satu untuk pelaporan penyelesaian kepada klien.

    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. Panggil metode InitializeDelegates di konstruktor komponen Anda.

    public PrimeNumberCalculator()
    {
        InitializeComponent();
    
        InitializeDelegates();
    }
    
    Public Sub New()
    
        InitializeComponent()
    
        InitializeDelegates()
    
    End Sub
    
  3. Deklarasikan delegasi di kelas PrimeNumberCalculator yang menangani pekerjaan aktual yang akan dilakukan secara asinkron. Delegasi ini membungkus metode pekerja yang menguji apakah angka itu prima. Delegasi mengambil parameter AsyncOperation, yang akan digunakan untuk melacak masa pakai operasi asinkron.

    private delegate void WorkerEventHandler(
        int numberToCheck,
        AsyncOperation asyncOp);
    
    Private Delegate Sub WorkerEventHandler( _
    ByVal numberToCheck As Integer, _
    ByVal asyncOp As AsyncOperation)
    
  4. Buat koleksi untuk mengelola masa pakai operasi asinkron yang tertunda. Klien membutuhkan cara untuk melacak operasi saat dijalankan dan diselesaikan, dan pelacakan ini dilakukan dengan mengharuskan klien untuk meneruskan token unik, atau ID tugas, ketika klien melakukan panggilan ke metode asinkron. Komponen PrimeNumberCalculator harus melacak setiap panggilan dengan mengaitkan ID tugas dengan pemanggilan yang sesuai. Jika klien meneruskan ID tugas yang tidak unik, komponen PrimeNumberCalculator harus memunculkan pengecualian.

    Komponen PrimeNumberCalculator melacak ID tugas dengan menggunakan kelas koleksi khusus yang disebut HybridDictionary. Dalam definisi kelas, buat HybridDictionary yang disebut userStateToLifetime.

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

Mengimplementasikan Peristiwa Publik

Komponen yang menerapkan Pola Asinkron berbasis Peristiwa berkomunikasi dengan klien menggunakan peristiwa. Peristiwa ini dipanggil pada utas yang tepat dengan bantuan kelas AsyncOperation.

Untuk menaikkan peristiwa ke klien komponen Anda:

  1. Mengimplementasikan peristiwa publik untuk pelaporan kepada klien. Anda akan memerlukan satu peristiwa untuk melaporkan kemajuan dan satu untuk penyelesaian pelaporan.

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

Mengimplementasikan Metode Penyelesaian

Delegasi penyelesaian adalah metode yang akan dipanggil perilaku asinkron berutas bebas yang mendasari ketika operasi asinkron berakhir dengan penyelesaian yang berhasil, kesalahan, atau pembatalan. Pemanggilan ini terjadi pada utas arbitrer.

Metode ini adalah tempat ID tugas klien dihapus dari koleksi internal token klien unik. Metode ini juga mengakhiri masa pakai operasi asinkron tertentu dengan memanggil metode PostOperationCompleted pada AsyncOperation yang sesuai. Panggilan ini menaikkan peristiwa penyelesaian pada utas yang sesuai untuk model aplikasi. Setelah metode PostOperationCompleted dipanggil, instans AsyncOperation ini tidak dapat lagi digunakan, dan upaya berikutnya untuk menggunakannya akan melemparkan pengecualian.

Tanda tangan CompletionMethod harus menahan semua status yang diperlukan untuk menjelaskan hasil operasi asinkron. Ini memegang status untuk nomor yang diuji oleh operasi asinkron khusus ini, apakah bilangannya prima, dan nilai pembagi pertamanya jika bukan bilangan prima. Ini juga memegang status yang menjelaskan pengecualian apa pun yang terjadi, dan AsyncOperation yang sesuai dengan tugas khusus ini.

Untuk menyelesaikan operasi asinkron:

  • Implementasikan metode penyelesaian. Dibutuhkan enam parameter, yang digunakan untuk mengisi CalculatePrimeCompletedEventArgs yang dikembalikan ke klien melalui CalculatePrimeCompletedEventHandler klien. Ini menghapus token ID tugas klien dari koleksi internal, dan mengakhiri masa pakai operasi asinkron dengan panggilan ke PostOperationCompleted. AsyncOperation membuat panggilan ke utas atau konteks yang sesuai untuk model aplikasi.

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

Titik pemeriksaan 2

Pada titik ini, Anda dapat membangun komponen.

Untuk menguji komponen Anda

  • Kompilasi komponen.

    Anda akan menerima satu peringatan pengompilasi:

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

    Peringatan ini akan diselesaikan di bagian berikutnya.

Mengimplementasikan Metode Pekerja

Sejauh ini, Anda telah menerapkan kode asinkron pendukung untuk komponen PrimeNumberCalculator. Sekarang Anda dapat mengimplementasikan kode yang melakukan pekerjaan yang sebenarnya. Anda akan mengimplementasikan tiga metode: CalculateWorker, BuildPrimeNumberList, dan IsPrime. Bersama-sama, BuildPrimeNumberList dan IsPrime terdiri dari algoritma terkenal yang disebut Saringan Eratosthenes, yang menentukan apakah sebuah bilangan adalah prima dengan menemukan semua bilangan prima hingga akar kuadrat dari bilangan pengujian. Jika tidak ada pembagi yang ditemukan pada saat itu, bilangan pengujian adalah prima.

Jika komponen ini ditulis untuk efisiensi maksimum, itu akan mengingat semua bilangan prima yang ditemukan oleh berbagai panggilan untuk bilangan pengujian yang berbeda. Ini juga akan memeriksa pembagi trivial seperti 2, 3, dan 5. Niat dari contoh ini adalah untuk menunjukkan bagaimana operasi yang memakan waktu dapat dijalankan secara asinkron, sehingga pengoptimalan ini dibiarkan sebagai latihan untuk Anda.

Metode CalculateWorker dibungkus dalam delegasi dan dipanggil secara asinkron dengan panggilan ke BeginInvoke.

Catatan

Pelaporan kemajuan diimplementasikan dalam metode BuildPrimeNumberList. Pada komputer yang cepat, peristiwa ProgressChanged dapat diangkat dengan suksesi yang cepat. Utas klien, tempat peristiwa ini diangkat, harus dapat menangani situasi ini. Kode antarmuka pengguna dapat dibanjiri dengan pesan dan tidak dapat mengikuti, mengakibatkan ketidakresponsifan. Untuk contoh antarmuka pengguna yang menangani situasi ini, lihat Cara: Mengimplementasikan Klien Pola Asinkron berbasis Peristiwa.

Untuk menjalankan perhitungan bilangan prima secara asinkron:

  1. Terapkan metode utilitas TaskCanceled. Ini memeriksa kumpulan seumur hidup tugas untuk ID tugas yang diberikan, dan mengembalikan true jika ID tugas tidak ditemukan.

    // 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. Mengimplementasikan metode CalculateWorker. Dibutuhkan dua parameter: angka untuk diuji, dan 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. Terapkan BuildPrimeNumberList. Dibutuhkan dua parameter: angka untuk diuji, dan AsyncOperation. Ini menggunakan AsyncOperation untuk melaporkan kemajuan dan hasil bertahap. Ini memastikan bahwa penanganan aktivitas klien dipanggil pada utas atau konteks yang tepat untuk model aplikasi. Ketika BuildPrimeNumberList menemukan bilangan prima, ia melaporkan ini sebagai hasil bertahap ke penanganan aktivitas klien untuk peristiwa ProgressChanged. Ini membutuhkan kelas yang berasal dari ProgressChangedEventArgs, yang disebut CalculatePrimeProgressChangedEventArgs, yang memiliki satu properti tambahan yang disebut LatestPrimeNumber.

    Metode BuildPrimeNumberList juga secara berkala memanggil metode TaskCanceled dan keluar jika metode mengembalikan 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. Implementasikan IsPrime. Dibutuhkan tiga parameter: daftar bilangan prima yang diketahui, angka untuk diuji, dan parameter output untuk pembagi pertama yang ditemukan. Mengingat daftar bilangan prima, ia menentukan apakah bilangan pengujian adalah prima.

    // 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. Dapatkan CalculatePrimeProgressChangedEventArgs dari ProgressChangedEventArgs. Kelas ini diperlukan untuk melaporkan hasil bertahap ke penanganan aktivitas klien untuk peristiwa ProgressChanged. Ini memiliki satu properti tambahan yang disebut 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
    

Titik pemeriksaan 3

Pada titik ini, Anda dapat membangun komponen.

Untuk menguji komponen Anda

  • Kompilasi komponen.

    Sisa yang harus ditulis adalah metode untuk memulai dan membatalkan operasi asinkron, CalculatePrimeAsync dan CancelAsync.

Menerapkan Metode Mulai dan Batal

Anda memulai metode pekerja pada utasnya sendiri dengan memanggil BeginInvoke pada delegasi yang membungkusnya. Untuk mengelola masa pakai operasi asinkron tertentu, Anda memanggil metode CreateOperation pada kelas pembantu AsyncOperationManager. Ini mengembalikan AsyncOperation, yang membuat panggilan pada penanganan aktivitas klien ke utas atau konteks yang tepat.

Anda membatalkan operasi tertunda tertentu dengan memanggil PostOperationCompleted pada AsyncOperation yang sesuai. Ini mengakhiri operasi itu, dan setiap panggilan berikutnya ke AsyncOperation akan melemparkan pengecualian.

Untuk menerapkan fungsi Start dan Cancel:

  1. Mengimplementasikan metode CalculatePrimeAsync. Pastikan token yang disediakan klien (ID tugas) unik sehubungan dengan semua token yang mewakili tugas yang saat ini tertunda. Jika klien meneruskan token yang tidak unik, CalculatePrimeAsync akan menimbulkan pengecualian. Jika tidak, token ditambahkan ke pengumpulan ID tugas.

    // 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. Mengimplementasikan metode CancelAsync. Jika parameter taskId ada dalam koleksi token, parameter tersebut akan dihapus. Ini mencegah tugas yang dibatalkan yang belum dimulai untuk dijalankan. Jika tugas berjalan, metode BuildPrimeNumberList keluar ketika mendeteksi bahwa ID tugas telah dihapus dari koleksi seumur hidup.

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

Titik pemeriksaan 4

Pada titik ini, Anda dapat membangun komponen.

Untuk menguji komponen Anda

  • Kompilasi komponen.

Komponen PrimeNumberCalculator sekarang selesai dan siap digunakan.

Untuk contoh klien yang menggunakan komponen PrimeNumberCalculator, lihat Cara: Mengimplementasikan Klien Pola Asinkron berbasis Peristiwa.

Langkah berikutnya

Anda dapat mengisi contoh ini dengan menulis CalculatePrime, setara dengan metode CalculatePrimeAsync yang sinkron. Ini akan membuat komponen PrimeNumberCalculator sepenuhnya sesuai dengan Pola Asinkron berbasis Peristiwa.

Anda dapat meningkatkan contoh ini dengan mempertahankan daftar semua bilangan prima yang ditemukan oleh berbagai panggilan untuk bilangan pengujian yang berbeda. Dengan menggunakan pendekatan ini, setiap tugas akan mendapat manfaat dari pekerjaan yang dilakukan oleh tugas-tugas sebelumnya. Berhati-hatilah untuk melindungi daftar ini dengan wilayah lock, sehingga akses ke daftar dengan utas yang berbeda diserialisasikan.

Anda juga dapat meningkatkan contoh ini dengan menguji pembagi trivial, seperti 2, 3, dan 5.

Lihat juga