방법: 이벤트 기반 비동기 패턴을 지원하는 구성 요소 구현

상당한 지연을 일으킬 수 있는 몇 가지 작업을 사용하여 클래스를 작성하는 경우 이벤트 기반 비동기 패턴 개요를 구현하여 비동기 기능을 부여하는 것을 고려할 수 있습니다.

이 연습에서는 이벤트 기반 비동기 패턴을 구현하는 구성 요소를 만드는 방법을 보여줍니다. ASP.NET, 콘솔 애플리케이션 및 Windows Forms 애플리케이션을 포함한 모든 애플리케이션 모델에서 구성 요소가 올바르게 작동하도록 하는 System.ComponentModel 네임스페이스의 도우미 클래스를 사용하는 것이 좋습니다. 이 구성 요소도 PropertyGrid 컨트롤과 고유한 사용자 지정 디자이너를 사용하여 디자인할 수 있습니다.

완료하면 소수를 비동기적으로 계산하는 애플리케이션이 생깁니다. 애플리케이션에는 기본 UI(사용자 인터페이스) 스레드 및 각 소수는 계산을 위한 스레드가 있습니다. 큰 숫자가 소수인지 테스트하는 데는 상당한 시간이 걸릴 수도 있지만 이 지연으로 기본 UI 스레드가 중단되지 않으며 계산 중에도 폼이 빠르게 응답하게 됩니다. 원하는 계산 수만큼 동시에 또는 선택적으로 취소 보류 계산을 실행할 수 있습니다.

이 연습에서 설명하는 작업은 다음과 같습니다.

  • 구성 요소 만들기

  • 공용 비동기 이벤트 및 대리자 정의

  • 전용 대리자 정의

  • 공용 이벤트 구현

  • 완료 메서드 구현

  • 작업자 메서드 구현

  • 시작 및 취소 메서드 구현

이 항목의 코드를 단일 목록으로 복사하려면 How to: Implement a Client of the Event-based Asynchronous Pattern(방법: 이벤트 기반 비동기 패턴의 클라이언트 구현)을 참조하세요.

구성 요소 만들기

첫 번째 단계는 이벤트 기반 비동기 패턴을 구현하는 구성 요소를 만드는 것입니다.

구성 요소를 만들려면

  • Component에서 상속되는 PrimeNumberCalculator라는 클래스를 만듭니다.

공용 비동기 이벤트 및 대리자 정의

구성 요소는 이벤트를 사용하는 클라이언트와 통신합니다. MethodNameCompleted 이벤트는 클라이언트에 비동기 작업의 완료를 알리고 MethodNameProgressChanged 이벤트는 클라이언트에 비동기 작업의 진행률을 알립니다.

구성 요소의 클라이언트에 대한 비동기 이벤트를 정의하려면:

  1. System.ThreadingSystem.Collections.Specialized 네임스페이스를 파일 맨 위로 가져옵니다.

    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. PrimeNumberCalculator 클래스 정의 전에 진행률 및 완료 이벤트에 대한 대리자를 선언합니다.

    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. PrimeNumberCalculator 클래스 정의에서 클라이언트에 진행률 및 완료를 보고하는 이벤트를 선언합니다.

    public event ProgressChangedEventHandler ProgressChanged;
    public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
    
    Public Event ProgressChanged _
        As ProgressChangedEventHandler
    Public Event CalculatePrimeCompleted _
        As CalculatePrimeCompletedEventHandler
    
  4. PrimeNumberCalculator 클래스 정의 후 각 계산의 결과를 CalculatePrimeCompleted.event에 대한 클라이언트 이벤트 처리기에 보고하기 위해 CalculatePrimeCompletedEventArgs 클래스를 파생시킵니다. AsyncCompletedEventArgs 속성 외에도 이 클래스를 사용하면 클라이언트가 테스트된 숫자, 숫자가 소수인지 여부 및 소수가 아닌 경우 첫 번째 제수를 확인할 수 있습니다.

    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
    

검사점 1

이때 구성 요소를 빌드할 수 있습니다.

구성 요소를 테스트하려면

  • 구성 요소를 컴파일합니다.

    두 개의 컴파일러 경고를 받게 됩니다.

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

    이러한 경고는 다음 섹션에서 지워집니다.

전용 대리자 정의

PrimeNumberCalculator 구성 요소의 비동기 측면은 SendOrPostCallback으로 알려진 특수 대리자를 사용하여 내부적으로 구현됩니다. SendOrPostCallbackThreadPool 스레드에서 실행되는 콜백 메서드를 나타냅니다. 콜백 메서드에는 Object 형식의 단일 매개 변수를 사용하는 시그니처가 있어야 합니다. 즉, 래퍼 클래스의 대리자 간에 상태를 전달해야 합니다. 자세한 내용은 SendOrPostCallback를 참조하세요.

구성 요소의 내부 비동기 동작을 구현하려면:

  1. PrimeNumberCalculator 클래스에 SendOrPostCallback 대리자를 선언하고 만듭니다. InitializeDelegates라는 유틸리티 메서드에서 SendOrPostCallback 개체를 만듭니다.

    각각 클라이언트에 진행률 및 완료를 보고하는 두 개의 대리자가 필요합니다.

    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. 구성 요소의 생성자에서 InitializeDelegates 메서드를 호출합니다.

    public PrimeNumberCalculator()
    {
        InitializeComponent();
    
        InitializeDelegates();
    }
    
    Public Sub New()
    
        InitializeComponent()
    
        InitializeDelegates()
    
    End Sub
    
  3. PrimeNumberCalculator 클래스에서 실제 작업이 비동기적으로 수행되도록 처리하는 대리자를 선언합니다. 이 대리자는 숫자가 소수인지 테스트하는 작업자 메서드를 래핑합니다. 대리자는 비동기 작업의 수명을 추적하는 데 사용되는 AsyncOperation 매개 변수를 사용합니다.

    private delegate void WorkerEventHandler(
        int numberToCheck,
        AsyncOperation asyncOp);
    
    Private Delegate Sub WorkerEventHandler( _
    ByVal numberToCheck As Integer, _
    ByVal asyncOp As AsyncOperation)
    
  4. 보류 중인 비동기 작업의 수명을 관리하기 위한 컬렉션을 만듭니다. 클라이언트에는 실행 및 완료 시 작업을 추적하는 방법이 필요하며, 이 추적을 수행하려면 클라이언트가 비동기 메서드를 호출할 때 고유한 토큰 또는 작업 ID를 전달해야 합니다. PrimeNumberCalculator 구성 요소는 작업 ID를 해당 호출과 연결하여 각 호출을 추적해야 합니다. 클라이언트가 고유하지 않은 작업 ID를 전달하면 PrimeNumberCalculator 구성 요소가 예외를 발생시켜야 합니다.

    PrimeNumberCalculator 구성 요소는 HybridDictionary라는 특수 컬렉션 클래스를 사용하여 작업 ID를 추적합니다. 클래스 정의에서 userStateToLifetime이라는 HybridDictionary를 만듭니다.

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

공용 이벤트 구현

이벤트 기반 비동기 패턴을 구현하는 구성 요소는 이벤트를 사용하는 클라이언트와 통신합니다. 이러한 이벤트는 AsyncOperation 클래스를 통해 적절한 스레드에서 호출됩니다.

구성 요소의 클라이언트에 이벤트를 발생시키려면:

  1. 클라이언트에 보고할 공용 이벤트를 구현합니다. 각각 진행률 및 완료를 보고하기 위한 두 개의 이벤트가 필요합니다.

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

완료 메서드 구현

완료 대리자는 비동기 작업이 성공적인 완료, 오류 또는 취소로 종료될 경우 기본적인 자유 스레드 비동기 동작이 호출되는 메서드입니다. 이 호출은 임의 스레드에서 발생합니다.

이 메서드는 클라이언트의 작업 ID가 고유한 클라이언트 토큰의 내부 컬렉션에서 제거되는 위치입니다. 또한 이 메서드는 해당 AsyncOperation에서 PostOperationCompleted 메서드를 호출하여 특정 비동기 작업의 수명을 종료합니다. 이 호출은 애플리케이션 모델에 적절한 스레드에서 완료 이벤트를 발생시킵니다. PostOperationCompleted 메서드를 호출한 후에는 AsyncOperation의 이 인스턴스를 더 이상 사용할 수 없고 이후에 이를 사용하려고 시도하면 예외가 throw됩니다.

CompletionMethod 시그니처는 비동기 작업의 결과를 설명하는 데 필요한 모든 상태를 포함해야 합니다. 이 시그니처는 이 특정 비동기 작업으로 테스트된 숫자의 상태, 숫자가 소수인지 여부 및 소수가 아닌 경우 첫 번째 제수의 값을 포함합니다. 또한 발생한 예외를 설명하는 상태 및 이 특정 작업에 해당하는 AsyncOperation을 포함합니다.

비동기 작업을 완료하려면:

  • 완료 메서드를 구현합니다. 이 메서드는 클라이언트의 CalculatePrimeCompletedEventHandler를 통해 클라이언트로 반환되는 CalculatePrimeCompletedEventArgs를 채우는 데 사용할 6개의 매개 변수를 사용합니다. 이 메서드는 내부 컬렉션에서 클라이언트의 작업 ID 토큰을 제거하고 PostOperationCompleted 호출을 통해 비동기 작업 수명을 종료합니다. AsyncOperation은 애플리케이션 모델에 적합한 스레드 또는 컨텍스트에 대한 호출을 마샬링합니다.

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

검사점 2

이때 구성 요소를 빌드할 수 있습니다.

구성 요소를 테스트하려면

  • 구성 요소를 컴파일합니다.

    하나의 컴파일러 경고를 받게 됩니다.

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

    이 경고는 다음 섹션에서 해결됩니다.

작업자 메서드 구현

지금까지 PrimeNumberCalculator 구성 요소에 대해 지원되는 비동기 코드를 구현했습니다. 이제 실제 작업을 수행하는 코드를 구현할 수 있습니다. 세 가지 메서드 CalculateWorker , BuildPrimeNumberListIsPrime을 구현합니다. BuildPrimeNumberListIsPrime은 함께 테스트 숫자의 제곱근까지 모든 소수를 찾아서 숫자가 소수인지 확인하는 에라토스테네스의 체라는 잘 알려진 알고리즘을 구성합니다. 해당 지점에서 제수를 찾을 수 없으면 테스트 숫자는 소수입니다.

효율성 최대화를 위해 이 구성 요소를 작성한 경우 이 구성 요소는 여러 테스트 숫자에 대한 다양한 호출을 통해 검색된 모든 소수를 기억합니다. 2, 3, 5와 같은 사소한 제수도 확인합니다. 그러나 이 예제의 목적은 시간이 오래 걸리는 작업을 비동기적으로 실행하는 방법을 보여주는 것이므로 이러한 최적화는 연습용으로 유지됩니다.

CalculateWorker 메서드는 대리자로 래핑되고 BeginInvoke 호출을 통해 비동기적으로 호출됩니다.

참고

진행률 보고는 BuildPrimeNumberList 메서드에서 구현됩니다. 빠른 컴퓨터에서는 ProgressChanged 이벤트가 연속적으로 발생할 수 있습니다. 이러한 이벤트가 발생하는 클라이언트 스레드에서 이 상황을 처리할 수 있어야 합니다. 사용자 인터페이스 코드가 메시지로 넘쳐 유지할 수 없어서 응답하지 않을 수 있습니다. 이 상황을 처리하는 예제 사용자 인터페이스는 방법: 이벤트 기반 비동기 패턴의 클라이언트 구현을 참조하세요.

소수 계산을 비동기적으로 실행하려면:

  1. TaskCanceled 유틸리티 메서드를 구현합니다. 이 메서드는 지정된 작업 ID의 작업 수명 컬렉션을 확인하고 작업 ID를 찾을 수 없는 경우 true를 반환합니다.

    // 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. CalculateWorker 메서드를 구현합니다. 이 메서드는 두 개의 매개 변수인 테스트할 숫자 및 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. BuildPrimeNumberList를 구현해야 합니다. 이 메서드는 두 개의 매개 변수인 테스트할 숫자 및 AsyncOperation을 사용합니다. AsyncOperation을 사용하여 진행률 및 증분 결과를 보고합니다. 이 메서드를 사용하면 클라이언트의 이벤트 처리기가 애플리케이션 모델에 대한 적절한 스레드 또는 컨텍스트에서 호출됩니다. BuildPrimeNumberList는 소수를 찾을 경우 ProgressChanged 이벤트에 대한 클라이언트 이벤트 처리기에 이 소수를 증분 결과로 보고합니다. 이 작업에는 LatestPrimeNumber라는 하나의 추가된 속성을 포함하는 CalculatePrimeProgressChangedEventArgs라는 ProgressChangedEventArgs에서 파생된 클래스가 필요합니다.

    또한 BuildPrimeNumberList 메서드는 TaskCanceled 메서드를 정기적으로 호출하고 메서드가 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. IsPrime를 구현해야 합니다. 이 메서드는 세 개의 매개 변수인 알려진 소수 목록, 테스트할 숫자 및 발견된 첫 번째 제수의 출력 매개 변수를 사용합니다. 소수 목록이 제공된 경우 테스트 숫자가 소수인지 확인합니다.

    // 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. ProgressChangedEventArgs에서 CalculatePrimeProgressChangedEventArgs를 파생시킵니다. 이 클래스는 ProgressChanged 이벤트에 대한 클라이언트 이벤트 처리기에 증분 결과를 보고하는 데 필요합니다. 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
    

검사점 3

이때 구성 요소를 빌드할 수 있습니다.

구성 요소를 테스트하려면

  • 구성 요소를 컴파일합니다.

    계속해서 비동기 작업을 시작 및 취소하는 CalculatePrimeAsyncCancelAsync 메서드를 작성합니다.

시작 및 취소 메서드 구현

메서드를 래핑하는 대리자에서 BeginInvoke를 호출하여 자체 스레드에서 작업자 메서드를 시작합니다. 특정 비동기 작업의 수명을 관리하려면 AsyncOperationManager 도우미 클래스에서 CreateOperation 메서드를 호출합니다. 이렇게 하면 클라이언트의 이벤트 처리기에 대한 호출을 올바른 스레드 또는 컨텍스트로 마샬링하는 AsyncOperation이 반환됩니다.

해당 AsyncOperation에서 PostOperationCompleted를 호출하여 특정 보류 작업을 취소합니다. 이렇게 하면 해당 작업이 종료되고 이후에 AsyncOperation을 호출하면 예외가 throw됩니다.

시작 및 취소 기능을 구현하려면:

  1. CalculatePrimeAsync 메서드를 구현합니다. 클라이언트 제공 토큰(작업 ID)이 현재 보류 중인 작업을 나타내는 모든 토큰에 관련해서 고유한지 확인합니다. 클라이언트가 고유하지 않은 토큰으로 전달되면 CalculatePrimeAsync가 예외를 발생시킵니다. 그렇지 않으면 토큰이 작업 ID 컬렉션에 추가됩니다.

    // 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. CancelAsync 메서드를 구현합니다. taskId 매개 변수가 토큰 컬렉션에 있는 경우 제거됩니다. 이렇게 하면 취소된 작업의 실행이 시작되지 않습니다. 작업이 실행 중인 경우 BuildPrimeNumberList 메서드는 해당 작업 ID가 수명 컬렉션에서 제거되었음을 감지할 때 종료됩니다.

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

검사점 4

이때 구성 요소를 빌드할 수 있습니다.

구성 요소를 테스트하려면

  • 구성 요소를 컴파일합니다.

이제 PrimeNumberCalculator 구성 요소가 완료되고 사용할 준비가 되었습니다.

PrimeNumberCalculator 구성 요소를 사용하는 예제 클라이언트는 방법: 이벤트 기반 비동기 패턴의 클라이언트 구현을 참조하세요.

다음 단계

CalculatePrimeAsync 메서드의 동기 메서드인 CalculatePrime을 작성하여 이 예제를 채울 수 있습니다. 이렇게 하면 PrimeNumberCalculator 구성 요소가 이벤트 기반 비동기 패턴을 완전히 준수하게 됩니다.

여러 테스트 숫자에 대한 다양한 호출을 통해 검색된 모든 소수 목록을 유지하면 이 예제를 향상할 수 있습니다. 이 방법을 사용하면 각 작업이 이전 작업에서 수행된 작업을 활용합니다. 이 목록을 lock 영역으로 보호하면 여러 스레드에 의한 목록 액세스가 직렬화되므로 주의해야 합니다.

2, 3, 5와 같은 사소한 제수를 테스트하여 이 예제를 향상할 수도 있습니다.

참고 항목