Share via


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

상당한 지연을 일으킬 수 있는 작업이 포함된 클래스를 쓸 경우 이벤트 기반 비동기 패턴 개요를 구현하여 비동기 기능을 주는 것이 좋습니다.

이 연습에서는 이벤트 기반 비동기 패턴을 구현하는 구성 요소를 만드는 방법을 설명합니다. 이 구성 요소는 ASP.NET, 콘솔 응용 프로그램 및 Windows Forms 응용 프로그램을 비롯한 모든 응용 프로그램 모델에서 제대로 작동할 수 있도록 System.ComponentModel 네임스페이스의 도우미 클래스를 사용하여 구현됩니다. 또한 이 구성 요소는 PropertyGrid 컨트롤과 자신만의 사용자 지정 디자이너를 사용하여 디자인할 수 있습니다.

이 연습을 마치면 소수를 비동기적으로 계산하는 응용 프로그램이 만들어집니다. 이 응용 프로그램에는 주 UI(사용자 인터페이스) 스레드와 각 소수 계산을 위한 스레드가 있습니다. 큰 수에 대하여 소수인지 여부를 테스트하려면 상당한 시간이 걸릴 수 있지만 이러한 시간 지연 때문에 주 UI 스레드가 중단되지는 않으며 계산 중에도 폼에서 응답할 수 있습니다. 여러 계산을 동시에 실행할 수 있으며 보류 중인 계산을 선택적으로 취소할 수 있습니다.

이 연습에서 수행할 작업은 다음과 같습니다.

  • 구성 요소 만들기

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

  • 전용 대리자 정의

  • 공용 이벤트 구현

  • 완료 메서드 구현

  • 작업자 메서드 구현

  • 시작 및 취소 메서드 구현

이 항목의 코드를 단일 목록으로 복사하려면 방법: 이벤트 기반 비동기 패턴을 지원하는 구성 요소 구현을 참조하십시오.

구성 요소 만들기

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

구성 요소를 만들려면

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

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

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

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

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

    Imports System
    Imports System.Collections
    Imports System.Collections.Specialized
    Imports System.ComponentModel
    Imports System.Drawing
    Imports System.Globalization
    Imports System.Threading
    Imports System.Windows.Forms
    
    using System;
    using System.Collections;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Globalization;
    using System.Threading;
    using System.Windows.Forms;
    
  2. PrimeNumberCalculator 클래스 정의 앞에 진행 및 완료 이벤트에 대해 대리자를 선언합니다.

    Public Delegate Sub ProgressChangedEventHandler( _
        ByVal e As ProgressChangedEventArgs)
    
    Public Delegate Sub CalculatePrimeCompletedEventHandler( _
        ByVal sender As Object, _
        ByVal e As CalculatePrimeCompletedEventArgs)
    
    public delegate void ProgressChangedEventHandler(
        ProgressChangedEventArgs e);
    
    public delegate void CalculatePrimeCompletedEventHandler(
        object sender,
        CalculatePrimeCompletedEventArgs e);
    
  3. PrimeNumberCalculator 클래스 정의에서 진행 및 완료를 클라이언트에 보고하는 이벤트를 선언합니다.

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

    Public Class CalculatePrimeCompletedEventArgs
        Inherits AsyncCompletedEventArgs
        Private numberToTestValue As Integer = 0
        Private firstDivisorValue As Integer = 1
        Private isPrimeValue As Boolean
    
    
        Public Sub New( _
        ByVal numberToTest As Integer, _
        ByVal firstDivisor As Integer, _
        ByVal isPrime As Boolean, _
        ByVal e As Exception, _
        ByVal canceled As Boolean, _
        ByVal state As Object)
    
            MyBase.New(e, canceled, state)
            Me.numberToTestValue = numberToTest
            Me.firstDivisorValue = firstDivisor
            Me.isPrimeValue = isPrime
    
        End Sub
    
    
        Public ReadOnly Property NumberToTest() As Integer
            Get
                ' Raise an exception if the operation failed 
                ' or was canceled.
                RaiseExceptionIfNecessary()
    
                ' If the operation was successful, return 
                ' the property value.
                Return numberToTestValue
            End Get
        End Property
    
    
        Public ReadOnly Property FirstDivisor() As Integer
            Get
                ' Raise an exception if the operation failed 
                ' or was canceled.
                RaiseExceptionIfNecessary()
    
                ' If the operation was successful, return 
                ' the property value.
                Return firstDivisorValue
            End Get
        End Property
    
    
        Public ReadOnly Property IsPrime() As Boolean
            Get
                ' Raise an exception if the operation failed 
                ' or was canceled.
                RaiseExceptionIfNecessary()
    
                ' If the operation was successful, return 
                ' the property value.
                Return isPrimeValue
            End Get
        End Property
    End Class
    
        public class CalculatePrimeCompletedEventArgs :
            AsyncCompletedEventArgs
        {
            private int numberToTestValue = 0;
            private int firstDivisorValue = 1;
            private bool isPrimeValue;
    
            public CalculatePrimeCompletedEventArgs(
                int numberToTest,
                int firstDivisor,
                bool isPrime,
                Exception e,
                bool canceled,
                object state) : base(e, canceled, state)
            {
                this.numberToTestValue = numberToTest;
                this.firstDivisorValue = firstDivisor;
                this.isPrimeValue = isPrime;
            }
    
            public int NumberToTest
            {
                get
                {
                    // Raise an exception if the operation failed or 
                    // was canceled.
                    RaiseExceptionIfNecessary();
    
                    // If the operation was successful, return the 
                    // property value.
                    return numberToTestValue;
                }
            }
    
            public int FirstDivisor
            {
                get
                {
                    // Raise an exception if the operation failed or 
                    // was canceled.
                    RaiseExceptionIfNecessary();
    
                    // If the operation was successful, return the 
                    // property value.
                    return firstDivisorValue;
                }
            }
    
            public bool IsPrime
            {
                get
                {
                    // Raise an exception if the operation failed or 
                    // was canceled.
                    RaiseExceptionIfNecessary();
    
                    // If the operation was successful, return the 
                    // property value.
                    return isPrimeValue;
                }
            }
        }
    
    

검사점

이 시점에서 구성 요소를 빌드할 수 있습니다.

구성 요소를 테스트하려면

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

    다음과 같은 두 개의 컴파일러 경고가 발생합니다.

    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 onProgressReportDelegate As SendOrPostCallback
    Private onCompletedDelegate As SendOrPostCallback
    
    
    ...
    
    
    Protected Overridable Sub InitializeDelegates()
        onProgressReportDelegate = _
            New SendOrPostCallback(AddressOf ReportProgress)
        onCompletedDelegate = _
            New SendOrPostCallback(AddressOf CalculateCompleted)
    End Sub
    
    private SendOrPostCallback onProgressReportDelegate;
    private SendOrPostCallback onCompletedDelegate;
    
    
    ...
    
    
    protected virtual void InitializeDelegates()
    {
        onProgressReportDelegate =
            new SendOrPostCallback(ReportProgress);
        onCompletedDelegate =
            new SendOrPostCallback(CalculateCompleted);
    }
    
  2. 구성 요소의 생성자에서 InitializeDelegates 메서드를 호출합니다.

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

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

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

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

공용 이벤트 구현

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

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

  • 클라이언트에 보고하는 공용 이벤트를 구현합니다. 진행률을 보고하는 이벤트와 완료를 보고하는 이벤트가 필요합니다.

    ' This method is invoked via the AsyncOperation object,
    ' so it is guaranteed to be executed on the correct thread.
    Private Sub CalculateCompleted(ByVal operationState As Object)
        Dim e As CalculatePrimeCompletedEventArgs = operationState
    
        OnCalculatePrimeCompleted(e)
    
    End Sub
    
    
    ' This method is invoked via the AsyncOperation object,
    ' so it is guaranteed to be executed on the correct thread.
    Private Sub ReportProgress(ByVal state As Object)
        Dim e As ProgressChangedEventArgs = state
    
        OnProgressChanged(e)
    
    End Sub
    
    Protected Sub OnCalculatePrimeCompleted( _
        ByVal e As CalculatePrimeCompletedEventArgs)
    
        RaiseEvent CalculatePrimeCompleted(Me, e)
    
    End Sub
    
    
    Protected Sub OnProgressChanged( _
        ByVal e As ProgressChangedEventArgs)
    
        RaiseEvent ProgressChanged(e)
    
    End Sub
    
    // This method is invoked via the AsyncOperation object,
    // so it is guaranteed to be executed on the correct thread.
    private void CalculateCompleted(object operationState)
    {
        CalculatePrimeCompletedEventArgs e =
            operationState as CalculatePrimeCompletedEventArgs;
    
        OnCalculatePrimeCompleted(e);
    }
    
    // This method is invoked via the AsyncOperation object,
    // so it is guaranteed to be executed on the correct thread.
    private void ReportProgress(object state)
    {
        ProgressChangedEventArgs e =
            state as ProgressChangedEventArgs;
    
        OnProgressChanged(e);
    }
    
    protected void OnCalculatePrimeCompleted(
        CalculatePrimeCompletedEventArgs e)
    {
        if (CalculatePrimeCompleted != null)
        {
            CalculatePrimeCompleted(this, e);
        }
    }
    
    protected void OnProgressChanged(ProgressChangedEventArgs e)
    {
        if (ProgressChanged != null)
        {
            ProgressChanged(e);
        }
    }
    

완료 메서드 구현

완료 대리자는 성공적인 완료, 오류 또는 취소로 비동기 작업이 끝날 때 자유 스레드된 내부 비동기 동작이 호출하는 메서드입니다. 이 호출은 임의의 스레드에서 발생합니다.

이 메서드를 통해 클라이언트의 작업 ID가 고유한 클라이언트 토큰의 내부 컬렉션에서 제거됩니다. 또한 이 메서드는 해당하는 AsyncOperationPostOperationCompleted 메서드를 호출하여 특정 비동기 작업의 수명을 끝냅니다. 이 호출은 응용 프로그램 모델에 적합한 스레드에서 완료 이벤트를 발생시킵니다. PostOperationCompleted 메서드가 호출된 후에는 이 AsyncOperation 인스턴스를 더 이상 사용할 수 없으며 이후에 사용하려고 하면 예외가 throw됩니다.

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

비동기 작업을 완료하려면

  • 완료 메서드를 구현합니다. 이 메서드는 6개의 매개 변수를 사용하여 클라이언트의 CalculatePrimeCompletedEventHandler를 통해 클라이언트에 반환되는 CalculatePrimeCompletedEventArgs를 채웁니다. 이 메서드는 내부 컬렉션에서 클라이언트의 작업 ID를 제거하고 PostOperationCompleted를 호출하여 비동기 작업의 수명을 끝냅니다. AsyncOperation은 응용 프로그램 모델에 적합한 스레드나 컨텍스트로 호출을 마샬링합니다.

    ' This is the method that the underlying, free-threaded 
    ' asynchronous behavior will invoke.  This will happen on
    '  an arbitrary thread.
    Private Sub CompletionMethod( _
        ByVal numberToTest As Integer, _
        ByVal firstDivisor As Integer, _
        ByVal prime As Boolean, _
        ByVal exc As Exception, _
        ByVal canceled As Boolean, _
        ByVal asyncOp As AsyncOperation)
    
        ' If the task was not previously canceled,
        ' remove the task from the lifetime collection.
        If Not canceled Then
            SyncLock userStateToLifetime.SyncRoot
                userStateToLifetime.Remove(asyncOp.UserSuppliedState)
            End SyncLock
        End If
    
        ' Package the results of the operation in a 
        ' CalculatePrimeCompletedEventArgs.
        Dim e As New CalculatePrimeCompletedEventArgs( _
            numberToTest, _
            firstDivisor, _
            prime, _
            exc, _
            canceled, _
            asyncOp.UserSuppliedState)
    
        ' End the task. The asyncOp object is responsible 
        ' for marshaling the call.
        asyncOp.PostOperationCompleted(onCompletedDelegate, e)
    
        ' Note that after the call to PostOperationCompleted, asyncOp
        ' is no longer usable, and any attempt to use it will cause.
        ' an exception to be thrown.
    
    End Sub
    
    // This is the method that the underlying, free-threaded 
    // asynchronous behavior will invoke.  This will happen on
    // an arbitrary thread.
    private void CompletionMethod( 
        int numberToTest,
        int firstDivisor, 
        bool isPrime,
        Exception exception, 
        bool canceled,
        AsyncOperation asyncOp )
    
    {
        // If the task was not previously canceled,
        // remove the task from the lifetime collection.
        if (!canceled)
        {
            lock (userStateToLifetime.SyncRoot)
            {
                userStateToLifetime.Remove(asyncOp.UserSuppliedState);
            }
        }
    
        // Package the results of the operation in a 
        // CalculatePrimeCompletedEventArgs.
        CalculatePrimeCompletedEventArgs e =
            new CalculatePrimeCompletedEventArgs(
            numberToTest,
            firstDivisor,
            isPrime,
            exception,
            canceled,
            asyncOp.UserSuppliedState);
    
        // End the task. The asyncOp object is responsible 
        // for marshaling the call.
        asyncOp.PostOperationCompleted(onCompletedDelegate, e);
    
        // Note that after the call to OperationCompleted, 
        // asyncOp is no longer usable, and any attempt to use it
        // will cause an exception to be thrown.
    }
    

검사점

이 시점에서 구성 요소를 빌드할 수 있습니다.

구성 요소를 테스트하려면

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

    다음과 같은 컴파일러 경고가 발생합니다.

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

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

작업자 메서드 구현

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

이 구성 요소가 효율성을 최대화하도록 작성된 경우에는 서로 다른 테스트 숫자에 대한 다양한 호출을 통하여 발견되는 모든 소수를 기억합니다. 또한 2, 3, 5와 같은 단순한 제수도 확인합니다. 그러나 이 예제는 시간이 많이 걸리는 작업을 비동기적으로 실행하는 방법을 보여 주기 위한 것이므로 이러한 최적화는 다루지 않습니다.

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

참고참고

진행률 보고는 BuildPrimeNumberList 메서드에서 구현됩니다.빠른 컴퓨터에서는 ProgressChanged 이벤트가 연속해서 빠르게 발생할 수 있습니다.이러한 이벤트가 발생하는 클라이언트 스레드에서는 이 상황을 처리할 수 있어야 합니다.사용자 인터페이스 코드에서 메시지가 많이 발생하여 제때 처리하지 못하면 작동이 중지될 수 있습니다.이 상환을 처리하는 예제 사용자 인터페이스는 방법: 이벤트 기반 비동기 패턴의 클라이언트 구현을 참조하십시오.

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

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

    ' Utility method for determining if a 
    ' task has been canceled.
    Private Function TaskCanceled(ByVal taskId As Object) As Boolean
        Return (userStateToLifetime(taskId) Is Nothing)
    End Function
    
    // Utility method for determining if a 
    // task has been canceled.
    private bool TaskCanceled(object taskId)
    {
        return( userStateToLifetime[taskId] == null );
    }
    
  2. CalculateWorker 메서드를 구현합니다. 이 메서드는 두 개의 매개 변수, 즉 테스트할 숫자 및 AsyncOperation을 사용합니다.

    ' This method performs the actual prime number computation.
    ' It is executed on the worker thread.
    Private Sub CalculateWorker( _
        ByVal numberToTest As Integer, _
        ByVal asyncOp As AsyncOperation)
    
        Dim prime As Boolean = False
        Dim firstDivisor As Integer = 1
        Dim exc As Exception = Nothing
    
        ' Check that the task is still active.
        ' The operation may have been canceled before
        ' the thread was scheduled.
        If Not Me.TaskCanceled(asyncOp.UserSuppliedState) Then
    
            Try
                ' Find all the prime numbers up to the
                ' square root of numberToTest.
                Dim primes As ArrayList = BuildPrimeNumberList( _
                    numberToTest, asyncOp)
    
                ' Now we have a list of primes less than 
                'numberToTest.
                prime = IsPrime( _
                    primes, _
                    numberToTest, _
                    firstDivisor)
    
            Catch ex As Exception
                exc = ex
            End Try
    
        End If
    
        Me.CompletionMethod( _
            numberToTest, _
            firstDivisor, _
            prime, _
            exc, _
            TaskCanceled(asyncOp.UserSuppliedState), _
            asyncOp)
    
    End Sub
    
    // This method performs the actual prime number computation.
    // It is executed on the worker thread.
    private void CalculateWorker(
        int numberToTest,
        AsyncOperation asyncOp)
    {
        bool isPrime = false;
        int firstDivisor = 1;
        Exception e = null;
    
        // Check that the task is still active.
        // The operation may have been canceled before
        // the thread was scheduled.
        if (!TaskCanceled(asyncOp.UserSuppliedState))
        {
            try
            {
                // Find all the prime numbers up to 
                // the square root of numberToTest.
                ArrayList primes = BuildPrimeNumberList(
                    numberToTest,
                    asyncOp);
    
                // Now we have a list of primes less than
                // numberToTest.
                isPrime = IsPrime(
                    primes,
                    numberToTest,
                    out firstDivisor);
            }
            catch (Exception ex)
            {
                e = ex;
            }
        }
    
        //CalculatePrimeState calcState = new CalculatePrimeState(
        //        numberToTest,
        //        firstDivisor,
        //        isPrime,
        //        e,
        //        TaskCanceled(asyncOp.UserSuppliedState),
        //        asyncOp);
    
        //this.CompletionMethod(calcState);
    
        this.CompletionMethod(
            numberToTest,
            firstDivisor,
            isPrime,
            e,
            TaskCanceled(asyncOp.UserSuppliedState),
            asyncOp);
    
        //completionMethodDelegate(calcState);
    }
    
  3. BuildPrimeNumberList를 구현합니다. 이 메서드는 두 개의 매개 변수, 즉 테스트할 숫자 및 AsyncOperation을 사용합니다. 또한 AsyncOperation을 사용하여 진행률과 증분 결과를 보고합니다. 이를 통해 클라이언트의 이벤트 처리기가 응용 프로그램 모델에 적합한 스레드나 컨텍스트에서 호출됩니다. BuildPrimeNumberList에서 소수를 찾으면 이를 ProgressChanged 이벤트의 클라이언트 이벤트 처리기에 증분 결과로 보고합니다. 이때 ProgressChangedEventArgs에서 파생된 CalculatePrimeProgressChangedEventArgs 클래스가 필요합니다. 이 클래스에는 LatestPrimeNumber라는 추가 속성이 하나 있습니다.

    또한 BuildPrimeNumberList 메서드는 정기적으로 TaskCanceled 메서드를 호출하며 이 메서드가 true를 반환하면 종료됩니다.

    ' This method computes the list of prime numbers used by the
    ' IsPrime method.
    Private Function BuildPrimeNumberList( _
        ByVal numberToTest As Integer, _
        ByVal asyncOp As AsyncOperation) As ArrayList
    
        Dim e As ProgressChangedEventArgs = Nothing
        Dim primes As New ArrayList
        Dim firstDivisor As Integer
        Dim n As Integer = 5
    
        ' Add the first prime numbers.
        primes.Add(2)
        primes.Add(3)
    
        ' Do the work.
        While n < numberToTest And _
            Not Me.TaskCanceled(asyncOp.UserSuppliedState)
    
            If IsPrime(primes, n, firstDivisor) Then
                ' Report to the client that you found a prime.
                e = New CalculatePrimeProgressChangedEventArgs( _
                    n, _
                    CSng(n) / CSng(numberToTest) * 100, _
                    asyncOp.UserSuppliedState)
    
                asyncOp.Post(Me.onProgressReportDelegate, e)
    
                primes.Add(n)
    
                ' Yield the rest of this time slice.
                Thread.Sleep(0)
            End If
    
            ' Skip even numbers.
            n += 2
    
        End While
    
        Return primes
    
    End Function
    
    // This method computes the list of prime numbers used by the
    // IsPrime method.
    private ArrayList BuildPrimeNumberList(
        int numberToTest,
        AsyncOperation asyncOp)
    {
        ProgressChangedEventArgs e = null;
        ArrayList primes = new ArrayList();
        int firstDivisor;
        int n = 5;
    
        // Add the first prime numbers.
        primes.Add(2);
        primes.Add(3);
    
        // Do the work.
        while (n < numberToTest && 
               !TaskCanceled( asyncOp.UserSuppliedState ) )
        {
            if (IsPrime(primes, n, out firstDivisor))
            {
                // Report to the client that a prime was found.
                e = new CalculatePrimeProgressChangedEventArgs(
                    n,
                    (int)((float)n / (float)numberToTest * 100),
                    asyncOp.UserSuppliedState);
    
                asyncOp.Post(this.onProgressReportDelegate, e);
    
                primes.Add(n);
    
                // Yield the rest of this time slice.
                Thread.Sleep(0);
            }
    
            // Skip even numbers.
            n += 2;
        }
    
        return primes;
    }
    
  4. IsPrime을 구현합니다. 이 메서드는 세 개의 매개 변수, 즉 알려진 소수 목록, 테스트할 숫자, 첫번째로 찾은 제수의 출력 매개 변수를 사용합니다. 소수 목록이 지정되면 이 메서드는 테스트 숫자가 소수인지 확인합니다.

    ' This method tests n for primality against the list of 
    ' prime numbers contained in the primes parameter.
    Private Function IsPrime( _
        ByVal primes As ArrayList, _
        ByVal n As Integer, _
        ByRef firstDivisor As Integer) As Boolean
    
        Dim foundDivisor As Boolean = False
        Dim exceedsSquareRoot As Boolean = False
    
        Dim i As Integer = 0
        Dim divisor As Integer = 0
        firstDivisor = 1
    
        ' Stop the search if:
        ' there are no more primes in the list,
        ' there is a divisor of n in the list, or
        ' there is a prime that is larger than 
        ' the square root of n.
        While i < primes.Count AndAlso _
            Not foundDivisor AndAlso _
            Not exceedsSquareRoot
    
            ' The divisor variable will be the smallest prime number 
            ' not yet tried.
            divisor = primes(i)
            i = i + 1
    
            ' Determine whether the divisor is greater than the 
            ' square root of n.
            If divisor * divisor > n Then
                exceedsSquareRoot = True
                ' Determine whether the divisor is a factor of n.
            ElseIf n Mod divisor = 0 Then
                firstDivisor = divisor
                foundDivisor = True
            End If
        End While
    
        Return Not foundDivisor
    
    End Function
    
    // This method tests n for primality against the list of 
    // prime numbers contained in the primes parameter.
    private bool IsPrime(
        ArrayList primes,
        int n,
        out int firstDivisor)
    {
        bool foundDivisor = false;
        bool exceedsSquareRoot = false;
    
        int i = 0;
        int divisor = 0;
        firstDivisor = 1;
    
        // Stop the search if:
        // there are no more primes in the list,
        // there is a divisor of n in the list, or
        // there is a prime that is larger than 
        // the square root of n.
        while (
            (i < primes.Count) &&
            !foundDivisor &&
            !exceedsSquareRoot)
        {
            // The divisor variable will be the smallest 
            // prime number not yet tried.
            divisor = (int)primes[i++];
    
            // Determine whether the divisor is greater
            // than the square root of n.
            if (divisor * divisor > n)
            {
                exceedsSquareRoot = true;
            }
            // Determine whether the divisor is a factor of n.
            else if (n % divisor == 0)
            {
                firstDivisor = divisor;
                foundDivisor = true;
            }
        }
    
        return !foundDivisor;
    }
    
  5. ProgressChangedEventArgs에서 CalculatePrimeProgressChangedEventArgs를 파생시킵니다. 이 클래스는 ProgressChanged 이벤트의 클라이언트 이벤트 처리기에 증분 결과를 보고하는 데 필요합니다. 이 클래스에는 LatestPrimeNumber라는 추가 속성이 하나 있습니다.

    Public Class CalculatePrimeProgressChangedEventArgs
        Inherits ProgressChangedEventArgs
        Private latestPrimeNumberValue As Integer = 1
    
    
        Public Sub New( _
            ByVal latestPrime As Integer, _
            ByVal progressPercentage As Integer, _
            ByVal UserState As Object)
    
            MyBase.New(progressPercentage, UserState)
            Me.latestPrimeNumberValue = latestPrime
    
        End Sub
    
        Public ReadOnly Property LatestPrimeNumber() As Integer
            Get
                Return latestPrimeNumberValue
            End Get
        End Property
    End Class
    
    public class CalculatePrimeProgressChangedEventArgs :
            ProgressChangedEventArgs
    {
        private int latestPrimeNumberValue = 1;
    
        public CalculatePrimeProgressChangedEventArgs(
            int latestPrime,
            int progressPercentage,
            object userToken) : base( progressPercentage, userToken )
        {
            this.latestPrimeNumberValue = latestPrime;
        }
    
        public int LatestPrimeNumber
        {
            get
            {
                return latestPrimeNumberValue;
            }
        }
    }
    

검사점

이 시점에서 구성 요소를 빌드합니다.

구성 요소를 테스트하려면

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

    이제 비동기 작업을 시작하거나 취소하는 CalculatePrimeAsync 및 CancelAsync 메서드만 쓰면 됩니다.

시작 및 취소 메서드 구현

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

보류 중인 특정 작업을 취소하려면 해당하는 AsyncOperationPostOperationCompleted를 호출합니다. 이렇게 하면 해당 작업이 끝나고 이후에 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 Overridable Sub CalculatePrimeAsync( _
        ByVal numberToTest As Integer, _
        ByVal taskId As Object)
    
        ' Create an AsyncOperation for taskId.
        Dim asyncOp As AsyncOperation = _
            AsyncOperationManager.CreateOperation(taskId)
    
        ' Multiple threads will access the task dictionary,
        ' so it must be locked to serialize access.
        SyncLock userStateToLifetime.SyncRoot
            If userStateToLifetime.Contains(taskId) Then
                Throw New ArgumentException( _
                    "Task ID parameter must be unique", _
                    "taskId")
            End If
    
            userStateToLifetime(taskId) = asyncOp
        End SyncLock
    
        ' Start the asynchronous operation.
        Dim workerDelegate As New WorkerEventHandler( _
            AddressOf CalculateWorker)
    
        workerDelegate.BeginInvoke( _
            numberToTest, _
            asyncOp, _
            Nothing, _
            Nothing)
    
    End Sub
    
    // This method starts an asynchronous calculation. 
    // First, it checks the supplied task ID for uniqueness.
    // If taskId is unique, it creates a new WorkerEventHandler 
    // and calls its BeginInvoke method to start the calculation.
    public virtual void CalculatePrimeAsync(
        int numberToTest,
        object taskId)
    {
        // Create an AsyncOperation for taskId.
        AsyncOperation asyncOp =
            AsyncOperationManager.CreateOperation(taskId);
    
        // Multiple threads will access the task dictionary,
        // so it must be locked to serialize access.
        lock (userStateToLifetime.SyncRoot)
        {
            if (userStateToLifetime.Contains(taskId))
            {
                throw new ArgumentException(
                    "Task ID parameter must be unique", 
                    "taskId");
            }
    
            userStateToLifetime[taskId] = asyncOp;
        }
    
        // Start the asynchronous operation.
        WorkerEventHandler workerDelegate = new WorkerEventHandler(CalculateWorker);
        workerDelegate.BeginInvoke(
            numberToTest,
            asyncOp,
            null,
            null);
    }
    
  2. CancelAsync 메서드를 구현합니다. 토큰 컬렉션에 taskId 매개 변수가 있으면 제거됩니다. 따라서 시작되지 않고 취소된 작업은 실행되지 않게 됩니다. 작업이 실행되고 있으면 BuildPrimeNumberList 메서드는 해당 작업 ID가 수명 컬렉션에서 제거되었음을 감지할 때 종료됩니다.

    ' This method cancels a pending asynchronous operation.
    Public Sub CancelAsync(ByVal taskId As Object)
    
        Dim obj As Object = userStateToLifetime(taskId)
        If (obj IsNot Nothing) Then
    
            SyncLock userStateToLifetime.SyncRoot
    
                userStateToLifetime.Remove(taskId)
    
            End SyncLock
    
        End If
    
    End Sub
    
    // This method cancels a pending asynchronous operation.
    public void CancelAsync(object taskId)
    {
        AsyncOperation asyncOp = userStateToLifetime[taskId] as AsyncOperation;
        if (asyncOp != null)
        {   
            lock (userStateToLifetime.SyncRoot)
            {
                userStateToLifetime.Remove(taskId);
            }
        }
    }
    

검사점

이 시점에서 구성 요소를 빌드합니다.

구성 요소를 테스트하려면

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

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

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

다음 단계

CalculatePrimeAsync 메서드와 동일한 동기 메서드인 CalculatePrime을 써서 이 예제에 추가할 수 있습니다. 이렇게 하면 PrimeNumberCalculator 구성 요소가 이벤트 기반 비동기 패턴과 완전히 호환됩니다.

여러 테스트 숫자에 대한 다양한 호출에서 발견된 모든 소수 목록을 유지하여 이 예제를 개선할 수 있습니다. 이 방법을 사용하면 각 작업에서 이전 작업의 결과를 활용할 수 있게 됩니다. 이 목록을 lock 영역으로 보호하여 목록에 대한 다른 스레드의 액세스가 serialize되게 합니다.

또한 2, 3, 5와 같이 간단한 제수를 대상으로 테스트하여 이 예제를 개선할 수 있습니다.

참고 항목

작업

방법: 백그라운드에서 작업 실행

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

개념

이벤트 기반 비동기 패턴 개요

기타 리소스

Multithreading in Visual Basic

이벤트 기반 비동기 패턴을 사용한 다중 스레드 프로그래밍