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

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


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



지난 포스트

 지난 포스트에서 EAP 라이브러리 만들기가 길어져서 중간 단계까지만 진행되었다. 이번에는 그 다음을 잰행해 보도록 하자. 


 이번 부터는 이전에 얘기 했던 delegate와 event를 가지고 Event를 발생시키는 클래스를 추가해야 한다. 이벤트 지향 프로그램은 UI가 있는 Platform에서 사용하기 쉽도록 하기 위해 Visual Basic에서 부터 도입이 된 방식이며 확장성 및 필터 개념처럼 수행되어 실행중에 이벤트를 이용해서 본래 데이터를 변경 및 추가 작업이 용이하게 할 수 있도록 지원한다. ASP.NET, Window, WPF의 기본 이벤트에서 "Loaded" 메소드가 있는 것을 알 수 있을 것이다. 이 메소드 또한 이벤트 방식으로 실행이 되며 우리는 이 이벤트에서 사용할 변수 초기화 또는 별도의 로직을 수행하는 코드를 작성하여 개발 한다. 이와 같이 현재는 이벤트 기반 개발 방식이 많은 Platform(ASP.NET, ASP.Net MVC, WinForm, WPF, Silverlight, ...)에서 사용되고 있는 방식이다.


 이제는 사용자 정의 event를 만드는 방법에 대해 간략하게 알아 보도록 하겠다. event는 delegate를 통해서 만들수 있으며 event를 코드에서 다시 선언 및 호출하여 사용자 정의 이벤트를 발생시킬 수 있다.

//delegate를 통해 입력 파라메터를 선언한다. private delegate void ObjectCompletedEventHandler(object senderAsyncCompletedEventArgs args); //선언된 delegate를 통해 event를 선언한다. private event ObjectCompletedEventHandler Completed;

[코드 1] delegate오 event 선언


"코드1"에서와 같이 선언하면 해당 클래스에서 "코드2"에서와 같이 사용할 수 있다.

//OnCompleted가 다른 곳에서 subscription되었는지 파악한다. //subscription하는 방법은 "OnCompleted += ThinkingAsyncEAP_OnCompleted;" 이다. if (Completed != null) {     //사용자 정의 이벤트일때 해당 파라메터를 세팅한다.     //.Net에서 기본적으로 제공하는 파라메터이다.     var args = new AsyncCompletedEventArgs(nullfalsenull);     //Completed를 실행하면 연결된 이벤트 핸들러로 제어권이 넘어 간다.     Completed(thisargs); }

[코드2] 사용자 정의 event 발생 시키기


 Completed의 선언된 이벤트는 Completed(this, args)와 같이 선언된 형식으로 호출 함으로써 발생 시킬 수 있으며 이벤트 변수는 꼭 null 체크를 해야 한다. 이벤트 객체는 클래스 외부에서 사용하도록 노출 시켜주는 특별한 delegate다. 그래서 해당 이벤트를 Subscription으로 등록하지 않으면 에러가 발생한다. 아래 "코드3"은 추가된 코드를 포함 한 전체 소스 코드이다.

/// <summary> /// 생각하는 비동기 클래스 EAP /// </summary> /// <remarks> /// EAP로 개발하기 준비 단계 /// 클래스 내부에서는 APM 형식으로 비동기를 수행하고 /// 클래스 외부에서는 Event 형식으로 수행이 되도록 지원해야 한다. /// 현재 코드는 아직 Event를 연결하지 않고 비동기 수행이 되는지 여부만 구현 하였다. /// </remarks> public class ThinkingAsyncEAP {     //delegate를 통해 입력 파라메터를 선언한다.     public delegate void ObjectCompletedEventHandler(object senderAsyncCompletedEventArgs args);     //선언된 delegate를 통해 event를 선언한다.     public event ObjectCompletedEventHandler Completed;     /// <summary>     /// 비동기 수행 시작하기     /// </summary>     /// <param name="action">action, delegate parameter</param>     /// <returns></returns>     public IAsyncResult StartAsync(Action action)     {         //APM 형식처럼 비동기로 실행 한다.         //delegate의 BeginInvoke와 동일 한다.         var asyncHandlerPointer = action.BeginInvoke(new AsyncCallback(AsyncCompleted), action);         //IAsyncResult를 반환 한다.         //APM에서 사용하던 기존 코드와의 호환성을 유지하기 위해 반환한다.         //호환성 유지가 필요 없으면 반환하지 않고 메소드를 void 형식으로 선언하면 된다.         return asyncHandlerPointer;     }     /// <summary>     /// 비동기 수행 종료하기     /// </summary>     /// <param name="result">IAsyncResult</param>     public void AsyncCompleted(IAsyncResult result)     {         //Action action = (Action)result.AsyncState;         //action.EndInvoke(result);        ManualResetEvent manualResetEvent = (ManualResetEvent)result.AsyncWaitHandle;         //OnCompleted가 다른 곳에서 subscription되었는지 파악한다.         //subscription하는 방법은 "OnCompleted += ThinkingAsyncEAP_OnCompleted;" 이다.         if (Completed != null)         {             //사용자 정의 이벤트일때 해당 파라메터를 세팅한다.             //.Net에서 기본적으로 제공하는 파라메터이다.             var args = new AsyncCompletedEventArgs(nullfalsenull);             //Completed를 실행하면 연결된 이벤트 핸들러로 제어권이 넘어 간다.             Completed(thisargs);         }

       manualResetEvent.Set(); //위치 변경     } }

[코드3] ThinkingAsyncEAP 업데이트 된 소스 코드


 "코드3"에서 음영 표시된이 이전코드에서 갱신된 코드다. 비 동기 수행이 완료가 되었을 때 클래스 외부로 사용자 정의 이벤트를 노출 시켜주는 기능을 추가 하였다. 이제 수정된 코드를 사용하는 법을 테스트 코드를 통해서 알아 보도록 하자.

/// <summary> /// 이벤트 기반 비동기 테스트 /// </summary> [TestMethod] public void Thinking_Event_Base_EAP_ASync_UnitTest() {     //Arrange step     //테스트 확인용 변수     int tempI = 0tempJ = 100;     //Act step     ThinkingAsyncEAP thinkingAsync = new ThinkingAsyncEAP();     //비 동기 수행 완료가 되었을 때 호출 할 이벤트     thinkingAsync.Completed += thinkingAsync_Completed;     var result = thinkingAsync.StartAsync(() =>     {         for (int i = 1i <= 100i++)         {             //50 밀리초씩 생각 한다.             Thread.Sleep(50);             Debug.WriteLine(i);             tempI = i;         }     });     //테스트를 확인하기 위해 비동기가 완료 할 때까지 대기. IAsyncResult를 리턴하도록 APM 환되도록 하여 사용할 수 있는 방안이다.     result.AsyncWaitHandle.WaitOne();     //Assert step     //값이 맞는지 테스트     Assert.AreEqual(tempItempJ); } void thinkingAsync_Completed(object senderAsyncCompletedEventArgs args) {     //비 동기 수행이 완료가 되었습니다. }

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


 "코드4" 추가된 이벤트 기능을 이용해 테스트를 하는 코드이며 thinkingAync.Completed += handlerMethod를 통해 완료할 때 실행될 핸들러를 등록 하였다. 수행 후 완료가 되면 해당 메소드를 실행하고 완료가 된다. 테스트 메소드에서의 코딩은 하나의 메소드에서 실행 및 테스트를 하기 위해 위와 같이 WaitOne() 메소드를 하출 하도록 하였지만 실제 코딩에서는 Completed 핸들러 메소드에서만 실행이 되도록 하면 되겠다.


 이번에도 코드가 너무 길어 지고 장황스럽게 되어 다음 포스트에서 이어 가도록 하겠다.







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

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


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



지난 포스트 


 이번 포스트는 지난 시간이 이은 두번째 EAP로 라이브러리를  알아 보도록 하겠습니다. 우선 EAP로 개발하기 위해서는 delegate와 event에 대해서 기본적인 선 지식이 있어야 이해가 편할 것으로 보이지만 전혀 모르는 상태에서도 전체적인 구성을 이해에는 별 무리가 없을 것입니다.


 다중 동시 작업 지원

한번에 한 작업만 지원 

 void MethodNameAsync([object userState]);  void MethodNameAsync();
 void CancelAsync([object userState]);  void CancelAsync();


 위와 같은 메소드로 비동기 수행을 실행할 수 있도록 규칙을 따라 함수를 만들어야 한다. ( Async의 object 파라메터는 옵션이다.) 자 이제 직접 코드로 살펴 보도록 하자. 하지만 아직까지는 Event 기반 클래스는 아니며 EAP를 만들기 위한 전 단계를 만들어 EAP로 가기 위한 전체적인 흐름을 쉽게 알 수 있도록 하겠다. 점차 점진적으로 EAP를 만들어 가 보자.

/// <summary>
/// 생각하는 비동기 클래스 EAP
/// </summary>
/// <remarks>
/// EAP로 개발하기 준비 단계
/// 클래스 내부에서는 APM 형식으로 비동기를 수행하고
/// 클래스 외부에서는 Event 형식으로 수행이 되도록 지원해야 한다.
/// 현재 코드는 아직 Event를 연결하지 않고 비동기 수행이 되는지 여부만 구현 하였다.
/// </remarks>
public class ThinkingAsyncEAP
{
    /// <summary>
    /// 비동기 수행 시작하기
    /// </summary>
    /// <param name="action">action, delegate parameter</param>
    /// <returns></returns>
    public IAsyncResult StartAsync(Action action)
    {
        //APM 형식처럼 비동기로 실행 한다.
        //delegate의 BeginInvoke와 동일 한다.
        var asyncHandlerPointer = action.BeginInvoke(new AsyncCallback(Completed), action);
 
        //IAsyncResult를 반환 한다.
        //APM에서 사용하던 기존 코드와의 호환성을 유지하기 위해 반환한다.
        //호환성 유지가 필요 없으면 반환하지 않고 메소드를 void 형식으로 선언하면 된다.
        return asyncHandlerPointer;
    }
 
    /// <summary>
    /// 비동기 수행 종료하기
    /// </summary>
    /// <param name="result">IAsyncResult</param>
    public void Completed(IAsyncResult result)
    {
        Action action = (Action)result.AsyncState;
        action.EndInvoke(result);
        //ManualResetEvent d = (ManualResetEvent)result.AsyncWaitHandle;
        //d.Set();
    }
}

[코드1] ThinkingAsyncEAP #1


 "코드 1"에서 delegate의 BeginInvoke를 이용하여 비동기 실행이 되도록 하였으며 IAsyncResult를 반환하여 기존에 APM으로 작성된 코드와도 호환이 되도록 하였다. 우선 코드 설명으로는 기본적으로 주석으로 대부분 설명 하였으며 전체적인 흐름은 StartAsync를 통해 비동기 실행한 구문을 받아 수행하고 완료가 되면 Completed 메소드가 수행하여 비동기 수행을 종료토록 한다. 다시 설명하지만 아직은 이벤트 기반으로 작성된 코드는 아니면 여기까지의 코드를 가지고 테스트에서 정상적으로 수행이 되는지 확인해 보도록 하자. 테스트 코드는 아래 "코드2"에 있다. 생각보다 시 수행 코드보다 테스트에서 확인하는 코드의 길이가 훨씬 긴 편인데 여러가지 케이스에 대해서 한번에 확인하기 위해 코드가 길어 졌다. 그렇지만 테스트 함수에서 사용한 코드를 통해 어떻게 사용하는지에 대해서 습득할 수 있는 여러가지 기법들을 알 수 있는 기회가 될 것이므로 이 역시 꼼꼼히 살펴 보도록 하자.

//Thinking메소드에서 사용할 변수(Assert 단계에서 정상 수행 확인 용도)
private int tempI2 = 0;
 
/// <summary>
/// EAP Library 만들기 #1
/// </summary>
/// <remarks>
/// TDD의 Unit Test는 하나의 메소드로 하나의 방법에 대해서만 테스트 하도록 권장하고 있다.
/// 그렇지만 여러가지 방안을 한번에 확인하고 쉽게 접근 및 이해가 편하도록 실행 여러가지 케이스를 한번에 Act, Assert 하도록 하였다.
/// </remarks>
[TestMethod]
public void Thinking_EAP_ASync_UnitTest()
{
    //Arrange step
    //테스트 확인용 변수
    int tempI = 0tempJ = 100;
 
    #region Action case
    //Act step
    var thinking1 = new ThinkingAsyncEAP();
    //별도의 쓰레드에서 실행
    var asyncHandlerPointer1 = thinking1.StartAsync(() =>
    {
        for (int i = 1i <= 100i++)
        {
            //50 밀리초씩 생각 한다.
            Thread.Sleep(50);
            Debug.WriteLine(i);
            tempI = i;
        }
    });
 
    Action action = () =>
    {
        for (int i = 1i <= 100i++)
        {
            //50 밀리초씩 생각 한다.
            Thread.Sleep(50);
            Debug.WriteLine(i);
            tempI = i;
        }
    };
 
    var thinking2 = new ThinkingAsyncEAP();
    //별도의 쓰레드에서 실행
    var asyncHandlerPointer2 = thinking2.StartAsync(action);
    #endregion
 
    #region delegate case
    var thinking3 = new ThinkingAsyncEAP();
    //별도의 쓰레드에서 실행
    var asyncHandlerPointer3 = thinking3.StartAsync(delegate()
    {
        for (int i = 1i <= 100i++)
        {
            //50 밀리초씩 생각 한다.
            Thread.Sleep(50);
            Debug.WriteLine(i);
            tempI = i;
        }
    });
    #endregion
 
    #region delegate case 2
    var thinking4 = new ThinkingAsyncEAP();
    //별도의 쓰레드에서 실행
    var asyncHandlerPointer4 = thinking4.StartAsync(Thinking);
    #endregion
 
    #region 별도의 로직
    //별도의 다른 로직 수행 시작
 
 
    //별도의 다른 로직 수행 종료
    #endregion
 
    ////대기가 필요 하면 WaitOne를 통해 쓰레드가 종료 될때까지 기다리고
    ////필요 없으면 주석처리 후 메소드를 종료 시키면 된다.
    //asyncHandlerPointer1.AsyncWaitHandle.WaitOne();
    //asyncHandlerPointer2.AsyncWaitHandle.WaitOne();
    //asyncHandlerPointer3.AsyncWaitHandle.WaitOne();
    //asyncHandlerPointer4.AsyncWaitHandle.WaitOne();
 
    //한번에 대기 하기 - 처리가 완료 될때까지 대기 한다. - 여기서는 assert단계에서 값을 확인하기 위해 대기하도록 하였다.
    //new[]는 컬렉션으로 만들어 주는 .net framework 3.5 문법이다.
    WaitHandle.WaitAll(new[]
    { 
        asyncHandlerPointer1.AsyncWaitHandle, 
        asyncHandlerPointer2.AsyncWaitHandle, 
        asyncHandlerPointer3.AsyncWaitHandle,
        asyncHandlerPointer4.AsyncWaitHandle
    });
 
    //Assert step
    //값이 맞는지 테스트
    //thread unsafe 하지만 마지막 실행 쓰레드가 
    //100으로 할당함으로 별도의 추가 작업을 하지 않겠음
    Assert.AreEqual(tempItempJ);
 
    //Assert step
    //thinking3.StartAsync(Thinking)는 closure 함수로 치환되지 않기에 
    //전역 함수를 사용해서 체크를 할 수 밖에 없었다.
    Assert.AreEqual(tempI2tempJ);
 
    //action, func, anomymouse delegate 함수는 컴파일러의 의해서
    //local 변수를 함수 내부로 포함되게 치환되어 사용할 수 있도록 된다.
    //그래서 아래와 같은 코드가 정상적으로 수행이 되는 것이다.
    //int i = 0;
    //() => { i++; } or delegate(){ i++; } 와 같이 선언되어서 바깥에서 선언된
    //i의 값을 내부에서 사용할 수 있도록 지원 한다.
}
 
/// <summary>
/// 시간끌기용 더미 메소드
/// </summary>
public void Thinking()
{
    for (int i = 1i <= 100i++)
    {
        //50초씩 생각 한다.
        Thread.Sleep(50);
        Debug.WriteLine(i);
        tempI2 = i;
    }
}

[코드 2] ThinkingAsyncEAP Unit Test


 위 "코드2"에서는 delegate, action, method point를 통해서 테스트를 진행하였으며 비동기로 수행할 로직은 단순하게 1부터 100까지 증가하는 로직을 테스트 하였다. 그리고 Debug.WriteLine를 통해 Visual studio의 "Output"창에서 확인 할 수 있도록 하였다. 그리고 한가지 더 덧 붙이자면 tempI2는 Clousure 함수와 같이 외부에서 선언된 메소드를 별도의 클래스의 내부에서 사용할 수 있는 방법이 없기 때문에 전역변수로 선언하여 사용 하여 테스트를 하였다.


 이번 포스트를 진행하다 보니 너무 길어 지는 문제점이 있엇 다음 포스트에서 계속 이어 가도록 하겠다. 너무 길어 지면 집중도와 이해가 낮아 지므로 다음에 계속 이어 가도록 하겠다.

+ Recent posts