방법: 이벤트 기반 비동기 패턴을 지원하는 구성 요소 구현How to: Implement a Component That Supports the Event-based Asynchronous Pattern

상당한 지연을 일으킬 수 있는 몇 가지 작업을 사용하여 클래스를 작성하는 경우 이벤트 기반 비동기 패턴 개요를 구현하여 비동기 기능을 부여하는 것을 고려할 수 있습니다.If you are writing a class with some operations that may incur noticeable delays, consider giving it asynchronous functionality by implementing the Event-based Asynchronous Pattern Overview.

이 연습에서는 이벤트 기반 비동기 패턴을 구현하는 구성 요소를 만드는 방법을 보여줍니다.This walkthrough illustrates how to create a component that implements the Event-based Asynchronous Pattern. ASP.NET, 콘솔 애플리케이션 및 Windows Forms 애플리케이션을 포함한 모든 애플리케이션 모델에서 구성 요소가 올바르게 작동하도록 하는 System.ComponentModel 네임스페이스의 도우미 클래스를 사용하는 것이 좋습니다.It is implemented using helper classes from the System.ComponentModel namespace, which ensures that the component works correctly under any application model, including ASP.NET, Console applications and Windows Forms applications. 이 구성 요소도 PropertyGrid 컨트롤과 고유한 사용자 지정 디자이너를 사용하여 디자인할 수 있습니다.This component is also designable with a PropertyGrid control and your own custom designers.

완료하면 소수를 비동기적으로 계산하는 애플리케이션이 생깁니다.When you are through, you will have an application that computes prime numbers asynchronously. 애플리케이션에는 기본 UI(사용자 인터페이스) 스레드 및 각 소수는 계산을 위한 스레드가 있습니다.Your application will have a main user interface (UI) thread and a thread for each prime number calculation. 큰 숫자가 소수인지 테스트하는 데는 상당한 시간이 걸릴 수도 있지만 이 지연으로 기본 UI 스레드가 중단되지 않으며 계산 중에도 폼이 빠르게 응답하게 됩니다.Although testing whether a large number is prime can take a noticeable amount of time, the main UI thread will not be interrupted by this delay, and the form will be responsive during the calculations. 원하는 계산 수만큼 동시에 또는 선택적으로 취소 보류 계산을 실행할 수 있습니다.You will be able to run as many calculations as you like concurrently and selectively cancel pending calculations.

이 연습에서 설명하는 작업은 다음과 같습니다.Tasks illustrated in this walkthrough include:

  • 구성 요소 만들기Creating the Component

  • 공용 비동기 이벤트 및 대리자 정의Defining Public Asynchronous Events and Delegates

  • 전용 대리자 정의Defining Private Delegates

  • 공용 이벤트 구현Implementing Public Events

  • 완료 메서드 구현Implementing the Completion Method

  • 작업자 메서드 구현Implementing the Worker Methods

  • 시작 및 취소 메서드 구현Implementing Start and Cancel Methods

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

구성 요소 만들기Creating the Component

첫 번째 단계는 이벤트 기반 비동기 패턴을 구현하는 구성 요소를 만드는 것입니다.The first step is to create the component that will implement the Event-based Asynchronous Pattern.

구성 요소를 만들려면To create the component

  • Component에서 상속되는 PrimeNumberCalculator라는 클래스를 만듭니다.Create a class called PrimeNumberCalculator that inherits from Component.

공용 비동기 이벤트 및 대리자 정의Defining Public Asynchronous Events and Delegates

구성 요소는 이벤트를 사용하는 클라이언트와 통신합니다.Your component communicates to clients using events. MethodNameCompleted 이벤트는 클라이언트에 비동기 작업의 완료를 알리고 MethodNameProgressChanged 이벤트는 클라이언트에 비동기 작업의 진행률을 알립니다.The MethodNameCompleted event alerts clients to the completion of an asynchronous task, and the MethodNameProgressChanged event informs clients of the progress of an asynchronous task.

구성 요소의 클라이언트에 대한 비동기 이벤트를 정의하려면:To define asynchronous events for clients of your component:

  1. System.ThreadingSystem.Collections.Specialized 네임스페이스를 파일 맨 위로 가져옵니다.Import the System.Threading and System.Collections.Specialized namespaces at the top of your file.

    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 클래스 정의 전에 진행률 및 완료 이벤트에 대한 대리자를 선언합니다.Before the PrimeNumberCalculator class definition, declare delegates for progress and completion events.

    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 클래스 정의에서 클라이언트에 진행률 및 완료를 보고하는 이벤트를 선언합니다.In the PrimeNumberCalculator class definition, declare events for reporting progress and completion to clients.

    public event ProgressChangedEventHandler ProgressChanged;
    public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
    
    Public Event ProgressChanged _
        As ProgressChangedEventHandler
    Public Event CalculatePrimeCompleted _
        As CalculatePrimeCompletedEventHandler
    
  4. PrimeNumberCalculator 클래스 정의 후 각 계산의 결과를 CalculatePrimeCompleted.event에 대한 클라이언트 이벤트 처리기에 보고하기 위해 CalculatePrimeCompletedEventArgs 클래스를 파생시킵니다.After the PrimeNumberCalculator class definition, derive the CalculatePrimeCompletedEventArgs class for reporting the outcome of each calculation to the client's event handler for the CalculatePrimeCompleted.event. AsyncCompletedEventArgs 속성 외에도 이 클래스를 사용하면 클라이언트가 테스트된 숫자, 숫자가 소수인지 여부 및 소수가 아닌 경우 첫 번째 제수를 확인할 수 있습니다.In addition to the AsyncCompletedEventArgs properties, this class enables the client to determine what number was tested, whether it is prime, and what the first divisor is if it is not prime.

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

검사점Checkpoint

이때 구성 요소를 빌드할 수 있습니다.At this point, you can build the component.

구성 요소를 테스트하려면To test your component

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

    두 개의 컴파일러 경고를 받게 됩니다.You will receive two compiler warnings:

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

    이러한 경고는 다음 섹션에서 지워집니다.These warnings will be cleared in the next section.

전용 대리자 정의Defining Private Delegates

PrimeNumberCalculator 구성 요소의 비동기 측면은 SendOrPostCallback으로 알려진 특수 대리자를 사용하여 내부적으로 구현됩니다.The asynchronous aspects of the PrimeNumberCalculator component are implemented internally with a special delegate known as a SendOrPostCallback. SendOrPostCallbackThreadPool 스레드에서 실행되는 콜백 메서드를 나타냅니다.A SendOrPostCallback represents a callback method that executes on a ThreadPool thread. 콜백 메서드에는 Object 형식의 단일 매개 변수를 사용하는 시그니처가 있어야 합니다. 즉, 래퍼 클래스의 대리자 간에 상태를 전달해야 합니다.The callback method must have a signature that takes a single parameter of type Object, which means you will need to pass state among delegates in a wrapper class. 자세한 내용은 SendOrPostCallback을 참조하세요.For more information, see SendOrPostCallback.

구성 요소의 내부 비동기 동작을 구현하려면:To implement your component's internal asynchronous behavior:

  1. PrimeNumberCalculator 클래스에 SendOrPostCallback 대리자를 선언하고 만듭니다.Declare and create the SendOrPostCallback delegates in the PrimeNumberCalculator class. InitializeDelegates라는 유틸리티 메서드에서 SendOrPostCallback 개체를 만듭니다.Create the SendOrPostCallback objects in a utility method called InitializeDelegates.

    각각 클라이언트에 진행률 및 완료를 보고하는 두 개의 대리자가 필요합니다.You will need two delegates: one for reporting progress to the client, and one for reporting completion to the client.

    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 메서드를 호출합니다.Call the InitializeDelegates method in your component's constructor.

    public PrimeNumberCalculator()
    {
        InitializeComponent();
    
        InitializeDelegates();
    }
    
    Public Sub New()
    
        InitializeComponent()
    
        InitializeDelegates()
    
    End Sub
    
  3. PrimeNumberCalculator 클래스에서 실제 작업이 비동기적으로 수행되도록 처리하는 대리자를 선언합니다.Declare a delegate in the PrimeNumberCalculator class that handles the actual work to be done asynchronously. 이 대리자는 숫자가 소수인지 테스트하는 작업자 메서드를 래핑합니다.This delegate wraps the worker method that tests whether a number is prime. 대리자는 비동기 작업의 수명을 추적하는 데 사용되는 AsyncOperation 매개 변수를 사용합니다.The delegate takes an AsyncOperation parameter, which will be used to track the lifetime of the asynchronous operation.

    private delegate void WorkerEventHandler(
        int numberToCheck,
        AsyncOperation asyncOp);
    
    Private Delegate Sub WorkerEventHandler( _
    ByVal numberToCheck As Integer, _
    ByVal asyncOp As AsyncOperation)
    
  4. 보류 중인 비동기 작업의 수명을 관리하기 위한 컬렉션을 만듭니다.Create a collection for managing lifetimes of pending asynchronous operations. 클라이언트에는 실행 및 완료 시 작업을 추적하는 방법이 필요하며, 이 추적을 수행하려면 클라이언트가 비동기 메서드를 호출할 때 고유한 토큰 또는 작업 ID를 전달해야 합니다.The client needs a way to track operations as they are executed and completed, and this tracking is done by requiring the client to pass a unique token, or task ID, when the client makes the call to the asynchronous method. PrimeNumberCalculator 구성 요소는 작업 ID를 해당 호출과 연결하여 각 호출을 추적해야 합니다.The PrimeNumberCalculator component must keep track of each call by associating the task ID with its corresponding invocation. 클라이언트가 고유하지 않은 작업 ID를 전달하면 PrimeNumberCalculator 구성 요소가 예외를 발생시켜야 합니다.If the client passes a task ID that is not unique, the PrimeNumberCalculator component must raise an exception.

    PrimeNumberCalculator 구성 요소는 HybridDictionary라는 특수 컬렉션 클래스를 사용하여 작업 ID를 추적합니다.The PrimeNumberCalculator component keeps track of task ID by using a special collection class called a HybridDictionary. 클래스 정의에서 userTokenToLifetime이라는 HybridDictionary를 만듭니다.In the class definition, create a HybridDictionary called userTokenToLifetime.

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

공용 이벤트 구현Implementing Public Events

이벤트 기반 비동기 패턴을 구현하는 구성 요소는 이벤트를 사용하는 클라이언트와 통신합니다.Components that implement the Event-based Asynchronous Pattern communicate to clients using events. 이러한 이벤트는 AsyncOperation 클래스를 통해 적절한 스레드에서 호출됩니다.These events are invoked on the proper thread with the help of the AsyncOperation class.

구성 요소의 클라이언트에 이벤트를 발생시키려면:To raise events to your component's clients:

  1. 클라이언트에 보고할 공용 이벤트를 구현합니다.Implement public events for reporting to clients. 각각 진행률 및 완료를 보고하기 위한 두 개의 이벤트가 필요합니다.You will need an event for reporting progress and one for reporting completion.

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

완료 메서드 구현Implementing the Completion Method

완료 대리자는 비동기 작업이 성공적인 완료, 오류 또는 취소로 종료될 경우 기본적인 자유 스레드 비동기 동작이 호출되는 메서드입니다.The completion delegate is the method that the underlying, free-threaded asynchronous behavior will invoke when the asynchronous operation ends by successful completion, error, or cancellation. 이 호출은 임의 스레드에서 발생합니다.This invocation happens on an arbitrary thread.

이 메서드는 클라이언트의 작업 ID가 고유한 클라이언트 토큰의 내부 컬렉션에서 제거되는 위치입니다.This method is where the client's task ID is removed from the internal collection of unique client tokens. 또한 이 메서드는 해당 AsyncOperation에서 PostOperationCompleted 메서드를 호출하여 특정 비동기 작업의 수명을 종료합니다.This method also ends the lifetime of a particular asynchronous operation by calling the PostOperationCompleted method on the corresponding AsyncOperation. 이 호출은 애플리케이션 모델에 적절한 스레드에서 완료 이벤트를 발생시킵니다.This call raises the completion event on the thread that is appropriate for the application model. PostOperationCompleted 메서드를 호출한 후에는 AsyncOperation의 이 인스턴스를 더 이상 사용할 수 없고 이후에 이를 사용하려고 시도하면 예외가 throw됩니다.After the PostOperationCompleted method is called, this instance of AsyncOperation can no longer be used, and any subsequent attempts to use it will throw an exception.

CompletionMethod 시그니처는 비동기 작업의 결과를 설명하는 데 필요한 모든 상태를 포함해야 합니다.The CompletionMethod signature must hold all state necessary to describe the outcome of the asynchronous operation. 이 시그니처는 이 특정 비동기 작업으로 테스트된 숫자의 상태, 숫자가 소수인지 여부 및 소수가 아닌 경우 첫 번째 제수의 값을 포함합니다.It holds state for the number that was tested by this particular asynchronous operation, whether the number is prime, and the value of its first divisor if it is not a prime number. 또한 발생한 예외를 설명하는 상태 및 이 특정 작업에 해당하는 AsyncOperation을 포함합니다.It also holds state describing any exception that occurred, and the AsyncOperation corresponding to this particular task.

비동기 작업을 완료하려면:To complete an asynchronous operation:

  • 완료 메서드를 구현합니다.Implement the completion method. 이 메서드는 클라이언트의 CalculatePrimeCompletedEventHandler를 통해 클라이언트로 반환되는 CalculatePrimeCompletedEventArgs를 채우는 데 사용할 6개의 매개 변수를 사용합니다.It takes six parameters, which it uses to populate a CalculatePrimeCompletedEventArgs that is returned to the client through the client's CalculatePrimeCompletedEventHandler. 이 메서드는 내부 컬렉션에서 클라이언트의 작업 ID 토큰을 제거하고 PostOperationCompleted 호출을 통해 비동기 작업 수명을 종료합니다.It removes the client's task ID token from the internal collection, and it ends the asynchronous operation's lifetime with a call to PostOperationCompleted. AsyncOperation은 애플리케이션 모델에 적합한 스레드 또는 컨텍스트에 대한 호출을 마샬링합니다.The AsyncOperation marshals the call to the thread or context that is appropriate for the application model.

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

검사점Checkpoint

이때 구성 요소를 빌드할 수 있습니다.At this point, you can build the component.

구성 요소를 테스트하려면To test your component

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

    하나의 컴파일러 경고를 받게 됩니다.You will receive one compiler warning:

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

    이 경고는 다음 섹션에서 해결됩니다.This warning will be resolved in the next section.

작업자 메서드 구현Implementing the Worker Methods

지금까지 PrimeNumberCalculator 구성 요소에 대해 지원되는 비동기 코드를 구현했습니다.So far, you have implemented the supporting asynchronous code for the PrimeNumberCalculator component. 이제 실제 작업을 수행하는 코드를 구현할 수 있습니다.Now you can implement the code that does the actual work. 세 가지 메서드 CalculateWorker , BuildPrimeNumberListIsPrime을 구현합니다.You will implement three methods: CalculateWorker, BuildPrimeNumberList, and IsPrime. BuildPrimeNumberListIsPrime은 함께 테스트 숫자의 제곱근까지 모든 소수를 찾아서 숫자가 소수인지 확인하는 에라토스테네스의 체라는 잘 알려진 알고리즘을 구성합니다.Together, BuildPrimeNumberList and IsPrime comprise a well-known algorithm called the Sieve of Eratosthenes, which determines if a number is prime by finding all the prime numbers up to the square root of the test number. 해당 지점에서 제수를 찾을 수 없으면 테스트 숫자는 소수입니다.If no divisors are found by that point, the test number is prime.

효율성 최대화를 위해 이 구성 요소를 작성한 경우 이 구성 요소는 여러 테스트 숫자에 대한 다양한 호출을 통해 검색된 모든 소수를 기억합니다.If this component were written for maximum efficiency, it would remember all the prime numbers discovered by various invocations for different test numbers. 2, 3, 5와 같은 사소한 제수도 확인합니다.It would also check for trivial divisors like 2, 3, and 5. 그러나 이 예제의 목적은 시간이 오래 걸리는 작업을 비동기적으로 실행하는 방법을 보여주는 것이므로 이러한 최적화는 연습용으로 유지됩니다.The intent of this example is to demonstrate how time-consuming operations can be executed asynchronously, however, so these optimizations are left as an exercise for you.

CalculateWorker 메서드는 대리자로 래핑되고 BeginInvoke 호출을 통해 비동기적으로 호출됩니다.The CalculateWorker method is wrapped in a delegate and is invoked asynchronously with a call to BeginInvoke.

참고

진행률 보고는 BuildPrimeNumberList 메서드에서 구현됩니다.Progress reporting is implemented in the BuildPrimeNumberList method. 빠른 컴퓨터에서는 ProgressChanged 이벤트가 연속적으로 발생할 수 있습니다.On fast computers, ProgressChanged events can be raised in rapid succession. 이러한 이벤트가 발생하는 클라이언트 스레드에서 이 상황을 처리할 수 있어야 합니다.The client thread, on which these events are raised, must be able to handle this situation. 사용자 인터페이스 코드가 메시지로 넘쳐 유지할 수 없어서 응답하지 않을 수 있습니다.User interface code may be flooded with messages and unable to keep up, resulting in unresponsiveness. 이 상황을 처리하는 예제 사용자 인터페이스는 방법: 이벤트 기반 비동기 패턴의 클라이언트 구현을 참조하세요.For an example user interface that handles this situation, see How to: Implement a Client of the Event-based Asynchronous Pattern.

소수 계산을 비동기적으로 실행하려면:To execute the prime number calculation asynchronously:

  1. TaskCanceled 유틸리티 메서드를 구현합니다.Implement the TaskCanceled utility method. 이 메서드는 지정된 작업 ID의 작업 수명 컬렉션을 확인하고 작업 ID를 찾을 수 없는 경우 true를 반환합니다.This checks the task lifetime collection for the given task ID, and returns true if the task ID is not found.

    // 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 메서드를 구현합니다.Implement the CalculateWorker method. 이 메서드는 두 개의 매개 변수인 테스트할 숫자 및 AsyncOperation을 사용합니다.It takes two parameters: a number to test, and an 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를 구현해야 합니다.Implement BuildPrimeNumberList. 이 메서드는 두 개의 매개 변수인 테스트할 숫자 및 AsyncOperation을 사용합니다.It takes two parameters: the number to test, and an AsyncOperation. AsyncOperation을 사용하여 진행률 및 증분 결과를 보고합니다.It uses the AsyncOperation to report progress and incremental results. 이 메서드를 사용하면 클라이언트의 이벤트 처리기가 애플리케이션 모델에 대한 적절한 스레드 또는 컨텍스트에서 호출됩니다.This assures that the client's event handlers are called on the proper thread or context for the application model. BuildPrimeNumberList는 소수를 찾을 경우 ProgressChanged 이벤트에 대한 클라이언트 이벤트 처리기에 이 소수를 증분 결과로 보고합니다.When BuildPrimeNumberList finds a prime number, it reports this as an incremental result to the client's event handler for the ProgressChanged event. 이 작업에는 LatestPrimeNumber라는 하나의 추가된 속성을 포함하는 CalculatePrimeProgressChangedEventArgs라는 ProgressChangedEventArgs에서 파생된 클래스가 필요합니다.This requires a class derived from ProgressChangedEventArgs, called CalculatePrimeProgressChangedEventArgs, which has one added property called LatestPrimeNumber.

    또한 BuildPrimeNumberList 메서드는 TaskCanceled 메서드를 정기적으로 호출하고 메서드가 true를 반환하는 경우 종료됩니다.The BuildPrimeNumberList method also periodically calls the TaskCanceled method and exits if the method returns 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를 구현해야 합니다.Implement IsPrime. 이 메서드는 세 개의 매개 변수인 알려진 소수 목록, 테스트할 숫자 및 발견된 첫 번째 제수의 출력 매개 변수를 사용합니다.It takes three parameters: a list of known prime numbers, the number to test, and an output parameter for the first divisor found. 소수 목록이 제공된 경우 테스트 숫자가 소수인지 확인합니다.Given the list of prime numbers, it determines if the test number is prime.

    // 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를 파생시킵니다.Derive CalculatePrimeProgressChangedEventArgs from ProgressChangedEventArgs. 이 클래스는 ProgressChanged 이벤트에 대한 클라이언트 이벤트 처리기에 증분 결과를 보고하는 데 필요합니다.This class is necessary for reporting incremental results to the client's event handler for the ProgressChanged event. LatestPrimeNumber라는 하나의 추가된 속성을 포함합니다.It has one added property called 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
    

검사점Checkpoint

이때 구성 요소를 빌드할 수 있습니다.At this point, you can build the component.

구성 요소를 테스트하려면To test your component

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

    계속해서 비동기 작업을 시작 및 취소하는 CalculatePrimeAsyncCancelAsync 메서드를 작성합니다.All that remains to be written are the methods to start and cancel asynchronous operations, CalculatePrimeAsync and CancelAsync.

시작 및 취소 메서드 구현Implementing the Start and Cancel Methods

메서드를 래핑하는 대리자에서 BeginInvoke를 호출하여 자체 스레드에서 작업자 메서드를 시작합니다.You start the worker method on its own thread by calling BeginInvoke on the delegate that wraps it. 특정 비동기 작업의 수명을 관리하려면 AsyncOperationManager 도우미 클래스에서 CreateOperation 메서드를 호출합니다.To manage the lifetime of a particular asynchronous operation, you call the CreateOperation method on the AsyncOperationManager helper class. 이렇게 하면 클라이언트의 이벤트 처리기에 대한 호출을 올바른 스레드 또는 컨텍스트로 마샬링하는 AsyncOperation이 반환됩니다.This returns an AsyncOperation, which marshals calls on the client's event handlers to the proper thread or context.

해당 AsyncOperation에서 PostOperationCompleted를 호출하여 특정 보류 작업을 취소합니다.You cancel a particular pending operation by calling PostOperationCompleted on its corresponding AsyncOperation. 이렇게 하면 해당 작업이 종료되고 이후에 AsyncOperation을 호출하면 예외가 throw됩니다.This ends that operation, and any subsequent calls to its AsyncOperation will throw an exception.

시작 및 취소 기능을 구현하려면:To implement Start and Cancel functionality:

  1. CalculatePrimeAsync 메서드를 구현합니다.Implement the CalculatePrimeAsync method. 클라이언트 제공 토큰(작업 ID)이 현재 보류 중인 작업을 나타내는 모든 토큰에 관련해서 고유한지 확인합니다.Make sure the client-supplied token (task ID) is unique with respect to all the tokens representing currently pending tasks. 클라이언트가 고유하지 않은 토큰으로 전달되면 CalculatePrimeAsync가 예외를 발생시킵니다.If the client passes in a non-unique token, CalculatePrimeAsync raises an exception. 그렇지 않으면 토큰이 작업 ID 컬렉션에 추가됩니다.Otherwise, the token is added to the task ID collection.

    // 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 메서드를 구현합니다.Implement the CancelAsync method. taskId 매개 변수가 토큰 컬렉션에 있는 경우 제거됩니다.If the taskId parameter exists in the token collection, it is removed. 이렇게 하면 취소된 작업의 실행이 시작되지 않습니다.This prevents canceled tasks that have not started from running. 작업이 실행 중인 경우 BuildPrimeNumberList 메서드는 해당 작업 ID가 수명 컬렉션에서 제거되었음을 감지할 때 종료됩니다.If the task is running, the BuildPrimeNumberList method exits when it detects that the task ID has been removed from the lifetime collection.

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

검사점Checkpoint

이때 구성 요소를 빌드할 수 있습니다.At this point, you can build the component.

구성 요소를 테스트하려면To test your component

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

이제 PrimeNumberCalculator 구성 요소가 완료되고 사용할 준비가 되었습니다.The PrimeNumberCalculator component is now complete and ready to use.

PrimeNumberCalculator 구성 요소를 사용하는 예제 클라이언트는 방법: 이벤트 기반 비동기 패턴의 클라이언트 구현을 참조하세요.For an example client that uses the PrimeNumberCalculator component, see How to: Implement a Client of the Event-based Asynchronous Pattern.

다음 단계Next Steps

CalculatePrimeAsync 메서드의 동기 메서드인 CalculatePrime을 작성하여 이 예제를 채울 수 있습니다.You can fill out this example by writing CalculatePrime, the synchronous equivalent of CalculatePrimeAsync method. 이렇게 하면 PrimeNumberCalculator 구성 요소가 이벤트 기반 비동기 패턴을 완전히 준수하게 됩니다.This will make the PrimeNumberCalculator component fully compliant with the Event-based Asynchronous Pattern.

여러 테스트 숫자에 대한 다양한 호출을 통해 검색된 모든 소수 목록을 유지하면 이 예제를 향상할 수 있습니다.You can improve this example by retaining the list of all the prime numbers discovered by various invocations for different test numbers. 이 방법을 사용하면 각 작업이 이전 작업에서 수행된 작업을 활용합니다.Using this approach, each task will benefit from the work done by previous tasks. 이 목록을 lock 영역으로 보호하면 여러 스레드에 의한 목록 액세스가 직렬화되므로 주의해야 합니다.Be careful to protect this list with lock regions, so access to the list by different threads is serialized.

2, 3, 5와 같은 사소한 제수를 테스트하여 이 예제를 향상할 수도 있습니다.You can also improve this example by testing for trivial divisors, like 2, 3, and 5.

참고 항목See also