최선의 이벤트 기반 비동기 패턴 구현 방법Best Practices for Implementing the Event-based Asynchronous Pattern

이벤트 기반 비동기 패턴은 익숙한 이벤트 및 대리자 의미 체계를 사용하여 클래스에 비동기 동작을 노출하는 효과적인 방법을 제공합니다.The Event-based Asynchronous Pattern provides you with an effective way to expose asynchronous behavior in classes, with familiar event and delegate semantics. 이벤트 기반 비동기 패턴을 구현하려면 몇 가지 구체적인 동작 요구 사항을 따라야 합니다.To implement Event-based Asynchronous Pattern, you need to follow some specific behavioral requirements. 다음 섹션에서는 이벤트 기반 비동기 패턴을 따르는 클래스를 구현할 때 고려해야 할 요구 사항 및 지침을 설명합니다.The following sections describe requirements and guidelines you should consider when you implement a class that follows the Event-based Asynchronous Pattern.

개요를 보려면 이벤트 기반 비동기 패턴 구현을 참조하세요.For an overview, see Implementing the Event-based Asynchronous Pattern.

필요한 동작 보장Required Behavioral Guarantees

이벤트 기반 비동기 패턴을 구현하는 경우 클래스가 올바르게 동작하고 클래스의 클라이언트가 해당 동작을 사용할 수 있도록 보장하는 다양한 보장을 제공해야 합니다.If you implement the Event-based Asynchronous Pattern, you must provide a number of guarantees to ensure that your class will behave properly and clients of your class can rely on such behavior.

완료Completion

성공적인 완료, 오류 또는 취소가 있는 경우 항상 MethodNameCompleted 이벤트 처리기를 호출합니다.Always invoke the MethodNameCompleted event handler when you have successful completion, an error, or a cancellation. 애플리케이션이 유휴 상태로 유지되고 절대 완료되지 않는 상황이 애플리케이션에 발생해서는 안 됩니다.Applications should never encounter a situation where they remain idle and completion never occurs. 이 규칙에 대한 한 가지 예외는 비동기 작업 자체가 절대 완료되지 않도록 디자인된 경우입니다.One exception to this rule is if the asynchronous operation itself is designed so that it never completes.

완료된 이벤트 및 EventArgsCompleted Event and EventArgs

별개의 각 MethodNameAsync 메서드의 경우 다음 디자인 요구 사항을 적용합니다.For each separate MethodNameAsync method, apply the following design requirements:

  • 동일한 클래스에서 MethodNameCompleted 이벤트를 메서드로 정의합니다.Define a MethodNameCompleted event on the same class as the method.

  • EventArgs 클래스에서 파생되는 MethodNameCompleted 이벤트에 대한 AsyncCompletedEventArgs 클래스 및 수반하는 대리자를 정의합니다.Define an EventArgs class and accompanying delegate for the MethodNameCompleted event that derives from the AsyncCompletedEventArgs class. 기본 클래스 이름은 MethodNameCompletedEventArgs 형식이어야 합니다.The default class name should be of the form MethodNameCompletedEventArgs.

  • EventArgs 클래스가 MethodName 메서드의 반환 값에 관련되도록 합니다.Ensure that the EventArgs class is specific to the return values of the MethodName method. EventArgs 클래스를 사용할 경우 개발자가 결과를 캐스팅할 필요가 없도록 해야 합니다.When you use the EventArgs class, you should never require developers to cast the result.

    다음 코드 예제에서는 각각 이 디자인 요구 사항의 좋은 구현과 나쁜 구현을 보여 줍니다.The following code example shows good and bad implementation of this design requirement respectively.

// Good design  
private void Form1_MethodNameCompleted(object sender, xxxCompletedEventArgs e)   
{   
    DemoType result = e.Result;  
}  
  
// Bad design  
private void Form1_MethodNameCompleted(object sender, MethodNameCompletedEventArgs e)   
{   
    DemoType result = (DemoType)(e.Result);  
}  
  • EventArgs를 반환하는 반환 메서드에 대해 void 클래스를 정의하지 마세요.Do not define an EventArgs class for returning methods that return void. 대신 AsyncCompletedEventArgs 클래스의 인스턴스를 사용하세요.Instead, use an instance of the AsyncCompletedEventArgs class.

  • 항상 MethodNameCompleted 이벤트가 발생하도록 합니다.Ensure that you always raise the MethodNameCompleted event. 이 이벤트는 성공적인 완료, 오류 또는 취소 시 발생해야 합니다.This event should be raised on successful completion, on an error, or on cancellation. 애플리케이션이 유휴 상태로 유지되고 절대 완료되지 않는 상황이 애플리케이션에 발생해서는 안 됩니다.Applications should never encounter a situation where they remain idle and completion never occurs.

  • 비동기 작업에서 발생하는 모든 예외를 catch하고 catch된 예외를 Error 속성에 할당하도록 합니다.Ensure that you catch any exceptions that occur in the asynchronous operation and assign the caught exception to the Error property.

  • 작업을 완료하는 중에 오류가 발생한 경우 결과에 액세스할 수 없습니다.If there was an error completing the task, the results should not be accessible. Error 속성이 null이 아닌 경우 EventArgs 구조의 모든 속성에 액세스할 때 예외가 발생해야 합니다.When the Error property is not null, ensure that accessing any property in the EventArgs structure raises an exception. RaiseExceptionIfNecessary 메서드를 사용하여 이 확인을 수행합니다.Use the RaiseExceptionIfNecessary method to perform this verification.

  • 제한 시간 초과를 오류로 모델링합니다.Model a time out as an error. 제한 시간 초과가 발생하면 MethodNameCompleted 이벤트를 발생시키고 TimeoutExceptionError 속성에 할당합니다.When a time out occurs, raise the MethodNameCompleted event and assign a TimeoutException to the Error property.

  • 클래스가 여러 동시 호출을 지원하는 경우 MethodNameCompleted 이벤트에 userSuppliedState 개체가 포함되도록 합니다.If your class supports multiple concurrent invocations, ensure that the MethodNameCompleted event contains the appropriate userSuppliedState object.

  • 적절한 스레드 및 애플리케이션 수명 주기의 적절한 시간에 MethodNameCompleted 이벤트가 발생하도록 합니다.Ensure that the MethodNameCompleted event is raised on the appropriate thread and at the appropriate time in the application lifecycle. 자세한 내용은 스레딩 및 컨텍스트 섹션을 참조하세요.For more information, see the Threading and Contexts section.

작업 동시 실행Simultaneously Executing Operations

  • 클래스가 여러 동시 호출을 지원하는 경우 개발자가 개체 반환 상태 매개 변수 또는 userSuppliedState라는 작업 ID를 사용하는 MethodNameAsync 오버로드를 별도로 정의하여 각 호출을 추적할 수 있도록 합니다.If your class supports multiple concurrent invocations, enable the developer to track each invocation separately by defining the MethodNameAsync overload that takes an object-valued state parameter, or task ID, called userSuppliedState. 이 매개 변수는 항상 MethodNameAsync 메서드 시그니처의 마지막 매개 변수여야 합니다.This parameter should always be the last parameter in the MethodNameAsync method's signature.

  • 클래스가 개체 반환 상태 매개 변수 또는 작업 ID를 사용하는 MethodNameAsync 오버로드를 정의하는 경우 해당 작업 ID로 작업의 수명을 추적해야 하며, 해당 작업 ID를 완료 처리기에 다시 제공해야 합니다.If your class defines the MethodNameAsync overload that takes an object-valued state parameter, or task ID, be sure to track the lifetime of the operation with that task ID, and be sure to provide it back into the completion handler. 도움이 되는 도우미 클래스가 있습니다.There are helper classes available to assist. 동시성 관리에 대한 자세한 내용은 방법: 이벤트 기반 비동기 패턴을 지 원하는 구성 요소 구현을 참조하세요.For more information on concurrency management, see How to: Implement a Component That Supports the Event-based Asynchronous Pattern.

  • 클래스가 상태 매개 변수 없이 MethodNameAsync 메서드를 정의하고 클래스가 여러 동시 호출을 지원하지 않는 경우 이전 MethodNameAsync 호출이 완료되기 전에 MethodNameAsync를 호출하려는 시도에서 InvalidOperationException이 발생하도록 합니다.If your class defines the MethodNameAsync method without the state parameter, and it does not support multiple concurrent invocations, ensure that any attempt to invoke MethodNameAsync before the prior MethodNameAsync invocation has completed raises an InvalidOperationException.

  • 일반적으로 처리 중인 작업이 여러 개 있도록 userSuppliedState 매개 변수 없이 MethodNameAsync 메서드를 여러 번 호출한 경우 예외를 발생시키지 마세요.In general, do not raise an exception if the MethodNameAsync method without the userSuppliedState parameter is invoked multiple times so that there are multiple outstanding operations. 클래스가 명시적으로 해당 상황을 처리할 수 없는 경우 예외를 발생시킬 수 있지만 개발자가 구분할 수 없는 이러한 여러 콜백을 처리할 수 있다고 가정합니다.You can raise an exception when your class explicitly cannot handle that situation, but assume that developers can handle these multiple indistinguishable callbacks

결과 액세스Accessing Results

진행률 보고Progress Reporting

  • 가능한 경우 진행률 보고를 지원합니다.Support progress reporting, if possible. 그러면 개발자가 클래스를 사용할 때 더 나은 애플리케이션 사용자 경험을 제공할 수 있습니다.This enables developers to provide a better application user experience when they use your class.

  • ProgressChanged 또는 MethodNameProgressChanged 이벤트를 구현하는 경우 특정 비동기 작업의 MethodNameCompleted 이벤트가 발생한 후 해당 작업에 대해 그러한 이벤트가 발생하지 않도록 합니다.If you implement a ProgressChanged or MethodNameProgressChanged event, ensure that there are no such events raised for a particular asynchronous operation after that operation's MethodNameCompleted event has been raised.

  • 표준 ProgressChangedEventArgs를 채우는 경우 ProgressPercentage가 항상 백분율로 해석될 수 있도록 합니다.If the standard ProgressChangedEventArgs is being populated, ensure that the ProgressPercentage can always be interpreted as a percentage. 백분율이 정확할 필요는 없지만 백분율을 나타내야 합니다.The percentage does not need to be accurate, but it should represent a percentage. 진행률 보고 메트릭이 백분율이 아닌 다른 메트릭이어야 하는 경우 ProgressChangedEventArgs 클래스에서 클래스를 파생시키고 ProgressPercentage를 0으로 둡니다.If your progress reporting metric must be something other than a percentage, derive a class from the ProgressChangedEventArgs class and leave ProgressPercentage at 0. 백분율이 아닌 보고 메트릭을 사용하는 것을 피하세요.Avoid using a reporting metric other than a percentage.

  • 적절한 스레드 및 애플리케이션 수명 주기의 적절한 시간에 ProgressChanged 이벤트가 발생하도록 합니다.Ensure that the ProgressChanged event is raised on the appropriate thread and at the appropriate time in the application lifecycle. 자세한 내용은 스레딩 및 컨텍스트 섹션을 참조하세요.For more information, see the Threading and Contexts section.

IsBusy 구현IsBusy Implementation

  • 클래스가 여러 동시 호출을 지원하는 경우 IsBusy 속성을 노출하지 마세요.Do not expose an IsBusy property if your class supports multiple concurrent invocations. 예를 들어, XML 웹 서비스 프록시는 비동기 메서드의 여러 동시 호출을 지원하므로 IsBusy 속성을 노출하지 않습니다.For example, XML Web service proxies do not expose an IsBusy property because they support multiple concurrent invocations of asynchronous methods.

  • IsBusy 속성은 MethodNameAsync 메서드가 호출된 후 MethodNameCompleted 이벤트가 발생하기 전에 true를 반환해야 합니다.The IsBusy property should return true after the MethodNameAsync method has been called and before the MethodNameCompleted event has been raised. 그렇지 않으면 false를 반환해야 합니다.Otherwise it should return false. BackgroundWorkerWebClient 구성 요소는 IsBusy 속성을 노출하는 클래스의 예입니다.The BackgroundWorker and WebClient components are examples of classes that expose an IsBusy property.

취소Cancellation

  • 가능한 경우 취소를 지원합니다.Support cancellation, if possible. 그러면 개발자가 클래스를 사용할 때 더 나은 애플리케이션 사용자 경험을 제공할 수 있습니다.This enables developers to provide a better application user experience when they use your class.

  • 취소의 경우 Cancelled 개체에 AsyncCompletedEventArgs 플래그를 설정합니다.In the case of cancellation, set the Cancelled flag in the AsyncCompletedEventArgs object.

  • 결과에 액세스하려는 모든 시도에서 작업이 취소되었음을 나타내는 InvalidOperationException이 발생하도록 합니다.Ensure that any attempt to access the result raises an InvalidOperationException stating that the operation was canceled. AsyncCompletedEventArgs.RaiseExceptionIfNecessary 메서드를 사용하여 이 확인을 수행합니다.Use the AsyncCompletedEventArgs.RaiseExceptionIfNecessary method to perform this verification.

  • 취소 메서드에 대한 호출은 항상 성공적으로 반환되고 예외를 발생시키지 않도록 합니다.Ensure that calls to a cancellation method always return successfully, and never raise an exception. 일반적으로 클라이언트는 어느 시점에서든 작업이 진정으로 취소 가능한지에 대한 알림을 받지 않으며, 이전에 발생한 취소가 성공적인지에 대한 알림도 받지 않습니다.In general, a client is not notified as to whether an operation is truly cancelable at any given time, and is not notified as to whether a previously issued cancellation has succeeded. 그러나 애플리케이션은 완료 상태에 참여하므로 취소가 성공적일 때 애플리케이션에는 항상 알림이 제공됩니다.However, the application will always be given notification when a cancellation succeeded, because the application takes part in the completion status.

  • 작업이 취소되면 MethodNameCompleted 이벤트를 발생시킵니다.Raise the MethodNameCompleted event when the operation is canceled.

오류 및 예외Errors and Exceptions

스레딩 및 컨텍스트Threading and Contexts

클래스가 올바로 작동하도록 하려면 ASP.NET 및 Windows Forms 애플리케이션을 비롯한 지정된 애플리케이션 모델에 대해 올바른 스레드 또는 컨텍스트에서 클라이언트의 이벤트 처리기가 호출되도록 해야 합니다.For correct operation of your class, it is critical that the client's event handlers are invoked on the proper thread or context for the given application model, including ASP.NET and Windows Forms applications. 모든 애플리케이션 모델에서 비동기 클래스가 올바르게 동작하도록 하기 위해 두 개의 중요한 도우미 클래스인 AsyncOperationAsyncOperationManager가 제공됩니다.Two important helper classes are provided to ensure that your asynchronous class behaves correctly under any application model: AsyncOperation and AsyncOperationManager.

AsyncOperationManagerCreateOperation을 반환하는 하나의 메서드 AsyncOperation을 제공합니다.AsyncOperationManager provides one method, CreateOperation, which returns an AsyncOperation. MethodNameAsync 메서드는 CreateOperation을 호출하고 클래스는 반환된 AsyncOperation을 사용하여 비동기 작업의 수명을 추적합니다.Your MethodNameAsync method calls CreateOperation and your class uses the returned AsyncOperation to track the lifetime of the asynchronous task.

클라이언트에 진행률, 증분 결과 및 완료를 보고하려면 Post에 대해 OperationCompletedAsyncOperation 메서드를 호출합니다.To report progress, incremental results, and completion to the client, call the Post and OperationCompleted methods on the AsyncOperation. AsyncOperation은 클라이언트의 이벤트 처리기에 대한 호출을 올바른 스레드 또는 컨텍스트로 마샬링하는 작업을 담당합니다.AsyncOperation is responsible for marshaling calls to the client's event handlers to the proper thread or context.

참고

명시적으로 애플리케이션 모델의 정책에 반대하되 이벤트 기반 비동기 패턴 사용의 다른 이점을 이용하려는 경우 이러한 규칙을 피해 갈 수 있습니다.You can circumvent these rules if you explicitly want to go against the policy of the application model, but still benefit from the other advantages of using the Event-based Asynchronous Pattern. 예를 들어, Windows Forms에서 작동하는 클래스가 자유 스레드가 되도록 할 수 있습니다.For example, you may want a class operating in Windows Forms to be free threaded. 개발자가 암시된 제한 사항을 이해하는 한 자유 스레드 클래스를 만들 수 있습니다.You can create a free threaded class, as long as developers understand the implied restrictions. 콘솔 애플리케이션은 Post 호출 실행을 동기화하지 않습니다.Console applications do not synchronize the execution of Post calls. 이로 인해 ProgressChanged 이벤트가 잘못 발생할 수 있습니다.This can cause ProgressChanged events to be raised out of order. Post 호출이 serialize되어 실행되도록 하려면 System.Threading.SynchronizationContext 클래스를 구현하여 설치합니다.If you wish to have serialized execution of Post calls, implement and install a System.Threading.SynchronizationContext class.

비동기 작업이 가능하도록 AsyncOperationAsyncOperationManager를 사용하는 방법에 대한 자세한 내용은 방법: 이벤트 기반 비동기 패턴을 지 원하는 구성 요소 구현을 참조하세요.For more information about using AsyncOperation and AsyncOperationManager to enable your asynchronous operations, see How to: Implement a Component That Supports the Event-based Asynchronous Pattern.

지침Guidelines

  • 이상적으로는 각 메서드 호출이 다른 메서드 호출과 별개여야 합니다.Ideally, each method invocation should be independent of others. 호출을 공유 리소스와 결합하는 것을 피해야 합니다.You should avoid coupling invocations with shared resources. 리소스가 호출 간에 공유되는 경우 구현에 적절한 동기화 메커니즘을 제공해야 합니다.If resources are to be shared among invocations, you will need to provide a proper synchronization mechanism in your implementation.

  • 클라이언트가 동기화를 구현해야 하는 디자인은 사용하지 않는 것이 좋습니다.Designs that require the client to implement synchronization are discouraged. 예를 들어, 전역 정적 개체를 매개 변수로 받는 비동기 메서드가 있을 수 있습니다. 이러한 메서드의 여러 동시 호출로 인해 데이터 손상이나 교착 상태가 발생할 수 있습니다.For example, you could have an asynchronous method that receives a global static object as a parameter; multiple concurrent invocations of such a method could result in data corruption or deadlocks.

  • 여러 호출 오버로드로 메서드를 구현하는 경우(시그니처에 userState가 있음) 클래스가 사용자 상태 또는 작업 ID 및 해당 보류 중인 작업의 컬렉션을 관리해야 합니다.If you implement a method with the multiple-invocation overload (userState in the signature), your class will need to manage a collection of user states, or task IDs, and their corresponding pending operations. 다양한 호출에서 컬렉션에 lock 개체를 추가 및 제거하므로 이 컬렉션은 userState 영역으로 보호해야 합니다.This collection should be protected with lock regions, because the various invocations add and remove userState objects in the collection.

  • 실현 가능하고 적절한 경우 CompletedEventArgs 클래스 재사용을 고려하세요.Consider reusing CompletedEventArgs classes where feasible and appropriate. 이 경우 지정된 대리자 및 EventArgs 형식이 단일 메서드에 연결된 것이 아니므로 명명이 메서드 이름과 일치하지 않습니다.In this case, the naming is not consistent with the method name, because a given delegate and EventArgs type are not tied to a single method. 그러나 개발자가 EventArgs에 대한 속성에서 검색된 값을 강제로 캐스팅하도록 하는 것은 절대 허용되지 않습니다.However, forcing developers to cast the value retrieved from a property on the EventArgs is never acceptable.

  • Component에서 파생되는 클래스를 작성하는 경우 고유한 SynchronizationContext 클래스를 구현하여 설치하지 마세요.If you are authoring a class that derives from Component, do not implement and install your own SynchronizationContext class. 구성 요소가 아니라 애플리케이션 모델이 사용되는 SynchronizationContext를 제어합니다.Application models, not components, control the SynchronizationContext that is used.

  • 모든 종류의 다중 스레딩을 사용할 때는 매우 심각하고 복잡한 버그에 잠재적으로 노출됩니다.When you use multithreading of any sort, you potentially expose yourself to very serious and complex bugs. 다중 스레딩을 사용하는 솔루션을 구현하기 전에 관리되는 스레딩을 구현하는 최선의 방법을 참조하세요.Before implementing any solution that uses multithreading, see Managed Threading Best Practices.

참고 항목See also