C# Event-based Asynchronous Pattern - EAP(4)

이벤트 기반 비동기 프로그래밍 패턴


http://msdn.microsoft.com/en-us/library/ms228969.aspx



지난 포스트

 지난 시간 포스트에서 thinkingAsync.Completed += thinkingAsync_Completed; 코드로 이벤트 핸들러를 등록하여 완료 이벤트가 발생 하였을 때 해당 핸들러 메소드에서 처리토록 하였다. 여기까지도 훌륭하다. 그렇지만 해당 핸들러의 메소드에서 받는 파라메터의 형식이 좀 아쉽다. 아래 "코드1"에서 보면 두번째 파라메터에서 받는 타입이 .Net Framework에서 제공하는 일반적인 기본 클래스를 넘겨 주도록 되어 있다.

void thinkingAsync_Completed(object senderAsyncCompletedEventArgs args)
{
    //취소 여부
    var a = args.Cancelled;
    //에러 메세지
    var b = args.Error;
    //멀티로 수행 시 구분 값
    var c = args.UserState;
}

[코드1] 비 동기 수행 완료시 실행되는 메소드


 해당 메소드의 코드를 보시면 세가지 타입만 얻어 올 수 있도록 제한이 되어 있다. EAP에서는 권고 사항으로 이벤트가 수행할 때 넘겨 받는 파라메터에서 형변환을 하지 않고 사용할 수 있도록 제공하는 것을 지향하고 있다. 그럼으로 이번 코드에서는 Generic 형식으로 만들어 보고 Action 형시으로 구성된 사항을 Func<T>로 재 구성해 보도록 하겠다. 지금까지의 코딩은 넘겨 받는 값이 없을 때 (void 타입) 사용하면 되는 케이스고 이번에는 비 동기 수행 후 넘겨 받는 파라메터가 있을 경우 사용하면 될 것이다. "코드2"를 보도록 하자.

/// <summary>
/// 생각하는 비동기 클래스 EAP - Generic class
/// </summary>
/// <remarks>
/// EAP로 개발하기 준비 단계
/// 클래스 내부에서는 APM 형식으로 비동기를 수행하고
/// 클래스 외부에서는 Event 형식으로 수행이 되도록 지원해야 한다.
/// 현재 코드는 아직 Event를 연결하지 않고 비동기 수행이 되는지 여부만 구현 하였다.
/// </remarks>
/// <example> 비 동기 수행 완료 이벤트 사용 예제
/// <code>
/// ThinkingAsyncEAP<int> thinkingAsync = new ThinkingAsyncEAP<int>();
/// thinkingAsync.Completed += thinkingAsync_Completed;
/// var result = thinkingAsync.StartAsync(() => { return 1;});
/// </code>
/// </example>
public class ThinkingAsyncEAP<T>
{
    /// <summary>
    /// 새로운 클래스에 맞는 Arguments의 타입을 만들기 위해 상속받아 재 정의.
    /// </summary>
    /// <remarks>
    /// inner class로 선언하여 한번에 Generic 클래스를 사용할 수 있도록 함.
    /// </remarks>
    public class ThinkingAsyncCompletedEventArgs : AsyncCompletedEventArgs
    {
        /// <summary>
        /// 사용자 정의 타입 값
        /// </summary>
        public T Result { getset; }
 
        /// <summary>
        /// 생성자에서는 별 기능을 하지 않고 바로 base 클래스로 넘겨 준다.
        /// </summary>
        /// <param name="error">에러 사항</param>
        /// <param name="cancelled">취소 여부</param>
        /// <param name="userState">쓰레드 구분자</param>
        public ThinkingAsyncCompletedEventArgs(Exception errorbool cancelledobject userState) : base(errorcancelleduserState)
        {
 
        }
    }
 
    //delegate를 통해 입력 파라메터를 선언한다.
    public delegate void ObjectCompletedEventHandler(object senderThinkingAsyncCompletedEventArgs args);
    //선언된 delegate를 통해 event를 선언한다.
    public event ObjectCompletedEventHandler Completed;
 
    /// <summary>
    /// 비동기 수행 시작하기
    /// </summary>
    /// <param name="func">action, delegate parameter</param>
    /// <returns></returns>
    public IAsyncResult StartAsync(Func<int> func)
    {
        //APM 형식처럼 비동기로 실행 한다.
        //delegate의 BeginInvoke와 동일 한다.
        var asyncHandlerPointer = func.BeginInvoke(new AsyncCallback(AsyncCompleted), func);
 
        //IAsyncResult를 반환 한다.
        //APM에서 사용하던 기존 코드와의 호환성을 유지하기 위해 반환한다.
        //호환성 유지가 필요 없으면 반환하지 않고 메소드를 void 형식으로 선언하면 된다.
        return asyncHandlerPointer;
    }
 
    /// <summary>
    /// 비동기 수행 종료하기
    /// </summary>
    /// <param name="result">IAsyncResult</param>
    public void AsyncCompleted(IAsyncResult result)
    {
        ManualResetEvent manualResetEvent = (ManualResetEvent)result.AsyncWaitHandle;
        //EndInvoke를 통해서 값을 넘겨 받고 대기자를 완료 한다.
        //manualResetEvent.Set()도 자동으로 수행이 된다.
        var tempResult = ((Func<T>)result.AsyncState).EndInvoke(result);

        //var asyncResult = (AsyncResult)result;
        ////BeginInvoke시 object를 넘겨 주지 안았을 경우 아래와 같은 방식으로도 실행자를 가져올 수 있다.
        //var tempResult = ((Func<T>)asyncResult.AsyncDelegate).EndInvoke(result);
        //OnCompleted가 다른 곳에서 subscription되었는지 파악한다.         //subscription하는 방법은 "OnCompleted += ThinkingAsyncEAP_OnCompleted;" 이다.         if (Completed != null)         {             //사용자 정의 이벤트일때 해당 파라메터를 세팅한다.             //.Net에서 기본적으로 제공하는 파라메터이다.             var args = new ThinkingAsyncCompletedEventArgs(nullfalsenull) {  Result = tempResult };             //Completed를 실행하면 연결된 이벤트 핸들러로 제어권이 넘어 간다.             Completed(thisargs);         }     } }

[코드2] Thinking Generic class


 위 "코드2"는 이전 포스트에서 알려 드렸던 코드에서 레이아웃에서 전체 흐름상으로는 크게 변경 되지 안았으나 반환값이 있어야 한다는 제약 조건 때문에 위와 같이 코드를 작성 하였다. Action대신 Func를 사용하였고 AsyncCompletedEventArgs를 상속받아 ThinkingAsyncCompletedEventArgs를 구현하였으며 비동기 수행 코드를 약간 수정하여 반환 할 수 있도록 하였다. 지금까지의 코드 자체의 완결성은 온전하지 않은 상태이며 개념을 이해 시키고자 하는 목적으로 코드를 작성 하였다. 이제 이 코드를 사용하는 테스트를 보도록 하자.

/// <summary>
/// 이벤트 기반 비동기 테스트
/// </summary>
[TestMethod]
public void Thinking_Event_Base_EAP_ASync_UnitTest()
{
    //Arrange step
    //테스트 확인용 변수
    int tempI = 0tempJ = 100;
 
    //Act step
    ThinkingAsyncEAP<int> thinkingAsync = new ThinkingAsyncEAP<int>();
    //비 동기 수행 완료가 되었을 때 호출 할 이벤트
    thinkingAsync.Completed += thinkingAsync_Completed;
    var result = thinkingAsync.StartAsync(() =>
    {
        int i = 1;
        for (; i <= 100i++)
        {
            //50 밀리초씩 생각 한다.
            Thread.Sleep(50);
            Debug.WriteLine(i);
            tempI = i;
        }
 
        return i;
    });
 
    //테스트를 확인하기 위해 비동기가 완료 할 때까지 대기.
    result.AsyncWaitHandle.WaitOne();
 
    //Assert step
    //값이 맞는지 테스트
    Assert.AreEqual(tempItempJ);
}
 
void thinkingAsync_Completed(object senderThinkingAsyncEAP<int>.ThinkingAsyncCompletedEventArgs args)
{
    //비 동기 수행이 완료가 되었습니다.
 
    //취소 여부
    var a = args.Cancelled;
    //에러 메세지
    var b = args.Error;
    //멀티로 수행 시 구분 값
    var c = args.UserState;
 
    //사용자 정의된 값
    var d = args.Result;    //101이 반환되지만 맞는 값이다. (for 구문때문에)
}

[코드3] 이벤트 기반 비동기 수행 테스트 코드


 "코드3"에서와 같이 수행 할 코드에서 나온 예제 형식으로 EAP 형식의 클래스를 선언하고 사용할 수 있다. 참고로 Inner class에 대해서는 http://blog.spowner.com/56 에서 설명을 읽어 보셔서 참고 하시면 되겠습니다.


 초반 예상보다 많은 포스트를 통해서 EAP에 대해 설명을 할 수 밖에 없었는데요. 라이브러리 개발자나 UI가 있는 상황에서 비 동기로 개발하면 일반 개발자가 사용 할 때 하나의 가이드 라인으로 제약할 수 있을것입니다. 그리고 이벤트는 기반으로 라이브러리르 구축함으로써 서로 다른 모듈간의 확장성을 확보할 수 있게 할 수 있습니다. 지금까지의 부족한 점이 많았지만 예제 및 주석, 간단한 설명을 통해 개념을 알려 드리고자 노력 하였습니다. 그렇지만 부족한 글 솜씨와 저의 한계로 많은 부분에서 미약한 부분이 많았으리라 생각이 듭니다. 그래도 최대한 노력하며 자체 검증을 통해 오류가 없도록 하였습니다. 감사합니다.

+ Recent posts