Semaphore(세마포어)


 세마포어는 동시에 엑세스할 수 있는 쓰레드 풀을 제한합니다. 다중 쓰레드에서 하나의 자원에 접근할 때 세마포어는 허용된 Concurrent 갯수 이상은 대기 토록하고 처리가 완료가 되면 완료된 쓰레드 갯수만큼 다음 쓰레드가 실행이 되도록 할 수 있습니다. 아래 "코드1"을 보시기 바랍니다.

//세파포어 선언 private static Semaphore _resourcePool; //한번에 허용할 수 있는 최대 쓰레드 수 private static int _maximumThreads = 3; [TestMethod] public void Semaphore_TestMethod() {     //세마포어를 할당한다.     //초기 실행 가능한 쓰레드는 0     //최대 실행 가능한 쓰레드는 3     _resourcePool = new Semaphore(0_maximumThreads);     for (int i = 0i < 8i++)     {         //쓰레드 할당         Thread thread = new Thread(Worker);         thread.Name = String.Concat("Thread "i + 1);         //쓰레드 실행         thread.Start();     }     //세마포어를 통해 대기되고 있던 쓰레드가 허용됫 갯수의 쓰레드가 수행이 되도록 한다.     _resourcePool.Release(_maximumThreads);             } /// <summary> /// 비동기 임의의 작업(3초 대기) /// </summary> private static void Worker() {     //진입된 쓰레드가 대기 하도록 한다.     _resourcePool.WaitOne();     Console.WriteLine("{0} Enters"Thread.CurrentThread.Name);     Debug.WriteLine("{0} Enters"Thread.CurrentThread.Name);     Thread.Sleep(3000);     Console.WriteLine("{0} Exits"Thread.CurrentThread.Name);     Debug.WriteLine("{0} Exits"Thread.CurrentThread.Name);     //진입된 쓰레드가 완료가 되었다고 알려준다.     _resourcePool.Release(); }

[코드1] 세마포어를 통해 동시 쓰레드 실행 갯수 제한


 위 코드에서 보는 바와 같이 최대 실행 갯수(_maximumThreads)를 3으로 세팅되었고, new Semaphore(0, _maximunThreads); 로 선언하였다. 이렇게 초기 세팅이 되면 Worker()안에서 WaitOne할 때 수행되는 쓰레드는 모두 대기 상태가 된다. 그렇지만 for문 마지막에 _resourcePool.Release(_maximunThreads);를 통해서 모두 실행 가능 하도록 하여 Wowker()의 WaitOne에서 대기하고 있던 쓰레드 3개가 수행이 되는 것이다. 이 세마포어를 통해 동시성 제어를 할 수 있다.


 Semaphore class는 커널 모드 객체를 통해 동작하기 때문에 lock, Monitor, Interlocked 보다 무거운 면이 있다 그렇지만 다음에 소개하는 SemaphoreSlim을 사용하면 가볍게 같은 방식으로 수행하도록 제어할 수 있다.

C# Task-based Asynchronous Pattern - TAP

http://msdn.microsoft.com/ko-kr/library/hh873175.aspx

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


http://www.albahari.com/threading/part2.aspx#_Thread_Safety



 Task-based Asynchronous Pattern(이하 TAP)은 .Net framework 4.0에서 나온 개념으로 저 수준의 Thread를 컨트롤하지 않고 고 수준의 Task 레벨에서 비동기를 지원하고 있고 이것을 이용해 비동기 패턴 구현을 권장하고 있다.

 TAP은 단일 메소드를 사용하여 비동기 작업의 시작과 완료를 나타냅니다.이는 APM(Begin, End)과 대조적이다. TAP 메소드는 반환되는 형식에 따라 Task, Task<T>로 반환되고 Task<T>로 반환된 객체는 Result 필드를 통해서 반환된 값을 사용할 수 있다. 우선 간단하게 Task를 이용해서 비 동기 실행 코드를 살펴 보도록 하자. 

/// <summary>
/// TAP 비 동기 수행 테스트
/// </summary>
[TestMethod]
public void TAP_TestMethod_01()
{
    //간단한 비 동기 실행 수행
    Task task = new Task(() => 
    {
        Thread.Sleep(1000);
        Debug.WriteLine("비 동기 실행.");
    });
 
    //타스크를 비동기 실행 시킨다.
    task.Start();
 
    //타스크가 완료가 될 때까지 대기 한다.
    task.Wait();
 
    Debug.WriteLine("비 동기 실행 완료.");
 
    //강제로 성공이라고 표시한다.
    Assert.IsTrue(true);
}

[코드1] Task를 이용해 비 동기 수행 


 "코드1"을 보면 Task를 인스턴스 시키면서 파라메터로 Action을 넘겨준다. 그리고 인스턴스에서 Start()를 호출하여 비 동기로 작업을 수행한다. task.Wait()는 비 동기 작업이 완료되기 전에 메소드를 종료 하는것을 방지 하기 위해 대기하도록 설정하였다. 위와 같은 방법은 이전의 Thread와 별 다를 바 없을 것이다. 그렇다면 다음 코드를 한번 보도록 하자.

/// <summary>
/// Task의 Factory를 통해 TAP 비 동기 수행 테스트
/// </summary>
[TestMethod]
public void TAP_TestMethod_02()
{
    //Task의 Factory를 통해서 타스크를 받아 오는 방법
    var task = Task.Factory.StartNew(() =>
    {
        Thread.Sleep(1000);
        Debug.WriteLine("비 동기 실행.");
    });
 
    //타스크가 완료가 될 때까지 대기 한다.
    task.Wait();
 
    Debug.WriteLine("비 동기 실행 완료.");
 
    //강제로 성공이라고 표시한다.
    Assert.IsTrue(true);
}

[코드2] Task의 Factory를 통해 Task 생성 후 실행


 "코드2"는 "코드1"과 다른점이 task.Start() 없이 곧바로 수행이 되도록 하고 있다. 그럼 다시 다음 코드를 보고 비교 해  보도록 하자. 

/// <summary>
/// Task<![CDATA[<T>]]>의 Factory를 통해 TAP 비 동기 수행 테스트
/// </summary>
[TestMethod]
public void TAP_TestMethod_03()
{
    var tempValue = 100;
 
    //Task의 Factory를 통해서 타스크를 받아 오는 방법
    var task = Task.Factory.StartNew(() =>
    {
        Thread.Sleep(1000);
        Debug.WriteLine("비 동기 실행.");
 
        return 100;
    });
 
    //비 동기 실행에서 값이 반활 될때까지 대기하고 
    //반환이 되면 returnValue에 값을 바인딩 한다.
    var returnValue = task.Result;
 
    Debug.WriteLine("비 동기 실행 완료.");
 
    //값이 같으면 성공
    Assert.AreEqual(returnValuetempValue);
}

[코드3] Task<T>형식으로 비 동기 작업 수행 테스트


 "코드3"에서는 지금까지와는 다른 task.Result를 사용하였다. 이 구문은 비 동기로 수행되는 작업이 완료되어 값을 반환 할 때까지 대기하고 값을 할당 해준다. 그런데 왜 비동기를 사용해야 할까? 비 동기를 사용 하면 낮설고, 교착 상태에 빠질 수도 있으며 디버깅 하기도 더 어렵다. 그렇다면 아래 "코드4"를 한번 보길 바란다.

/// <summary>
/// Task<![CDATA[<T>]]>의 Factory를 통해 TAP 비 동기 수행 테스트
/// 여러 타스크를 비 동기로 수행하여 시간 절약을 얻었다.
/// </summary>
[TestMethod]
public void TAP_TestMethod_04()
{
    var tempValue = 450;
 
    Stopwatch sw = new Stopwatch();
    sw.Start();
 
    //Task의 Factory를 통해서 타스크를 받아 오는 방법
    var task1 = Task.Factory.StartNew(() =>
    {
        Thread.Sleep(2000);
        Debug.WriteLine("DB1.Server.com에서 DB를 가지고 옴.");
 
        return 50;
    });
 
    //Task의 Factory를 통해서 타스크를 받아 오는 방법
    var task2 = Task.Factory.StartNew(() =>
    {
        Thread.Sleep(3500);
        Debug.WriteLine("Socket로 레거시 시스템에서 정보를 가져 옴");
 
        return 150;
    });
 
    //Task의 Factory를 통해서 타스크를 받아 오는 방법
    var task3 = Task.Factory.StartNew(() =>
    {
        Thread.Sleep(1500);
        Debug.WriteLine("DB2.Server.com에서 DB를 저장하고 영향 받은 row 갯수를 가져옴.");
 
        return 250;
    });
 
    //비 동기 실행에서 값이 반활 될때까지 대기한다.
    Task.WaitAll(new[] { task1task2task3 });
 
    sw.Stop();
    Debug.WriteLine(sw.ElapsedMilliseconds);  //3504밀리초가 output에 찍혔다.
    Debug.WriteLine("비 동기 실행 완료.");
 
    //returnValue에 비 동기로 수행해 받은 값을 바인딩 한다.
    var returnValue = task1.Result + task2.Result + task3.Result;
 
    //값이 같으면 성공
    Assert.AreEqual(returnValuetempValue);
}

[코드4] 비 동기 수행으로 전체 수행 시간을 빠르게 할 수 있다.


 위 코드를 살펴 보면 외부 시스템(DB, Socket)으로 정보를 가져오고 저장하는 프로세스가 있다. 각 수행 시간이 2000, 3500, 1500 밀리초가 걸린다고 가졍하며 코딩을 하였다. 이 작업을 순차적으로 수행하면 2000 + 3500 + 1500을 모두 합한 7초가 걸린다는 산술적인 계산이 떨어 진다. 그렇지만 위 작업을 비 동기로 수행하면 최대 공약수인 3500밀리초만 걸리다는 것을 알 수 있다. 위 코드에서 sw.ElapsedMilliseconds 코드의 결과값이 3504(컴퓨터 마다 약간의 차이가 발생한다)로 측정이 되었다. 7초와 3.5초라는 단순 비교 만으로도 비 동기 수행을 함으로써 얻을 수 있는 잇점을 장황한 설명을 하지 않아도 알 수 있을 것이다.



Tip!

나중에 ASP.NET MVC에서 IAsyncController을 통해서 비동기 컨트롤을 사용하여 얻을 수 있는 잇점에 대해서도 상세하게 다루도록 하겠다.




 지금까지 TAP를 이용해서 비 동기 작업에 대해서 간단하게 탐색해 보았다. 다음 포스트에서는 지금보다 더 상세하게 알아보는 시간을 가져보도록 하겠다.


------------------------------------------------------------------------------------------


 아래는 MS에서 배포하고 있는 TAP관련 문서이며 번역번도 같이 올려 놓는다. - 번역본은 판교 (이전 가락동)에서 교육을 받을 때 강습생들이 번역한 문서다.


TAP(Task-based Asynchronous Pattern).docx

TAP(Task-based Asynchronous Pattern)(번역본)completed.docx


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

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 함수와 같이 외부에서 선언된 메소드를 별도의 클래스의 내부에서 사용할 수 있는 방법이 없기 때문에 전역변수로 선언하여 사용 하여 테스트를 하였다.


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

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

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


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


 이벤트 기반 비동기 패턴은 APM에서 문제(dead lock, cancellation)가 될 수 있는 사항을 피할 수 있습니다. 이 패턴을 지원하는 가이드로 개발하면 다음 작업을 수월히 수행할 수 있습니다. ( - 이벤트 기반 비동기 프로그래밍은 UI가 있는 어플리케이션에서 주로 사용한다. 라이브러리가 제공되는 Async 메소드를 실행하면 Completed나 Changed 이벤트를 받아서 후속 작업을 처리하면 된다. WinForm이나 WPF에서 많이 사용 한다. - EAP를 사용 해야 할 때 - )

  • 다운로드와 데이터베이스 작업 등 시간이 많이 걸리는 작업을 중단하지 않고 "백그라운드에서" 수행

  • 여러 작업을 동시에 실행하며 각 작업이 완료될 때 알림을 받음

  • 응용 프로그램을 중지하지 않고 리소스를 사용할 수 있을 때까지 대기

  • 익숙한 이벤트 및 대리자 모델을 사용하여 보류 중인 비동기 작업과 통신

 이벤트 기반 비동기 패턴은 MethodAsync, MethodCompleted, MethodCancel로 제공되며 이 메소드로 각 작업을 수행하며 위 사항을 지원 한다. 우선 예제를 통해 눈으로 확인을 해봐야 대략적으로 알 수 있을 것이다. 아래 예제를 보도록 하자.

BackgroundWorker bw = new BackgroundWorker();
//RunWorkerAsync() 메소드가 실행되 면 비동기로 실행 될 코드
bw.DoWork += bw_DoWork;
//완료시 처리할 이벤트 핸들로
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
//상태가 변경되면  처리할 이벤트 핸들로
//통상적으로 진행율을 표시하는 걸로 사용 한다.
bw.ProgressChanged += bw_ProgressChanged;
 
//비동기로 메소드 실행
bw.RunWorkerAsync();
            
//비동기 작업을 취소 할 때 사용 한다.
//bw.CancelAsync();

[코드 1] 비동 메소드 사용 예제


 BackgroundWorker 클래스는 일반적인 사항에서 비동기적으로 처리할 일을 대신 맡아서 처리하는 클래스다 일반적인 케이스에서는 이 클래스를 사용해서 비 동기 작업을 진행하면 무리가 없을 것이다. 이 클래스는 Winform, ASP.NET, WPF에서 효과적으로 동작 한다.


EAP를 제공하는 라이브러리를 위와 같이 사용하며 전체 소스는 아래와 같다.

/// <summary> /// 진행율 표시 /// </summary> private int inc; /// <summary> /// 이벤트 기반 비동기 테스트 /// </summary> [TestMethod] public void BackgroundWorker_EAP_ASync() {     BackgroundWorker bw = new BackgroundWorker();     //RunWorkerAsync() 메소드가 실행되 면 비동기로 실행 될 코드     bw.DoWork += bw_DoWork;     //완료시 처리할 이벤트 핸들로     bw.RunWorkerCompleted += bw_RunWorkerCompleted;     //상태가 변경되면  처리할 이벤트 핸들로     //통상적으로 진행율을 표시하는 걸로 사용 한다.     bw.ProgressChanged += bw_ProgressChanged; //진행상황을 보고 하도록 서정 함(ProgressChanged 이벤트가 동작 한다)     bw.WorkerReportsProgress = true;

    //비동기로 메소드 실행     bw.RunWorkerAsync();     do

    {         //비동기가 끝날때까지 대기         if (bw.IsBusy)         {             Thread.Sleep(1000);         }         else         {

//비동기 작업이 종료되면 loop 밖으로 나간다.             break;         }     } while (true);

    //비동기 작업을 취소 할 때 사용 한다.     //bw.CancelAsync();     Assert.AreEqual(inc100); } /// <summary> /// 백그라운드의 상태가 변경되었을 때 상태값을 파라메터로 넘겨 받아 처리 한다. /// </summary> /// <param name="sender">BackgroundWorker</param> /// <param name="e">ProgressChangedEventArgs</param> void bw_ProgressChanged(object senderProgressChangedEventArgs e) {     int progress = e.ProgressPercentage;     //진행율을 표기 한다. } /// <summary> /// 비동기로 실제 작업을 수행하는 코드 /// </summary> /// <param name="sender">BackgroundWorker</param> /// <param name="e">DoWorkEventArgs</param> void bw_DoWork(object senderDoWorkEventArgs e) {     inc = 0;     BackgroundWorker worker = (BackgroundWorker)sender;     do     {         //취소 여부를 체크         if (worker.CancellationPending)         {             e.Cancel = true;    //취소로 상태 변경             break;  //loop out         }         else         {             inc++;             worker.ReportProgress(inc);         }         Thread.Sleep(100);     } while (!e.Cancel && inc < 100); //취소 신호가 있으면 바로 나간다, 완료되면 out } /// <summary> /// 비 동기 작업수행이 완료가 되면 실행 된다. /// </summary> /// <param name="sender">BackgroundWorker</param> /// <param name="e">RunWorkerCompletedEventArgs</param> void bw_RunWorkerCompleted(object senderRunWorkerCompletedEventArgs e) {     if (e.Cancelled)     {         //취소 메세지를 처리 한다.         Console.WriteLine("취소 됨");     }     else     {         //완료에 해당하는 작업을 수행한다.         Console.WriteLine("정상적으로 완료");     } }


 EAP 방식은 위에 나타난 소스 말고도 이벤트 핸들러에 필수 적인 EventsArgs를 상속받은 객체를 세가지 더 구현해야 하는 불편함이 있다. 라이브러리를 사용하는 프로그래머는 어렵지 않게 제공되는 형식의 이벤트대로 구현을 하면 되지만 라이브러리를 만드는 프로그래머는 신경을 써야 하는 부분도 많다. 그렇지만 추상적인 흐름을 만들어 두어 그 안에서 비동기를 지원하도록 하기 때문에 사용자가 쉽게 접근하여 비동기 프로그래밍을 할 수 있도록 강제 또는 가이드를 할 수 있는 이점이 있다. 


 위 케이스는 하나의 객체에서 여러번의 일을 비동기로 수행할 수 있는 방안은 아니며 "userState" 객체를 이용해서 여러번 수행 할 수 있도록 구분할 수 있는 매커니즘을 구현해야 한다.


 다음 포스트에서는 EAP 라이브러리를 만드는 작업에 대해서 알아 보도록 하겠다.

C# Asynchronous Programming Model - APM

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

http://msdn.microsoft.com/ko-kr/library/ms228963.aspx




APM은 BeginMethod <--> EndMethod를 쌍으로 엮여진 메소드로 비동기를 구현하는 모델이다. 이미 .Net frameworkd에서는 파일이나 네트워크 관련으로 메소드를 APM(begin, end) 방식으로 비 동기 메소드를 구현하였다. 아래 그림1에서 확인 할 수 있다.


[그림1] Begin으로 시작하는 메소드


 그림1에서와 같이 System.IO와 System.Net 하위 클래스에서 Begin으로 시작하는 메소드가 많으며 이 메소드와 쌍으로 End로 시작하는 메소드가 있을 것이다.

(해당 그림은 Object Browser에서 검색하여 나온 결과 화면이다.)



 예제로 파일을 동기와 비 동기로 저장하는 방법을 알아 보도록 하자.



동기식 파일 저장 예제

[TestMethod]
public void AsyncUnitTest_APM_Sync()
{
    var contents = "컨텐츠 내용, 컨텐츠 내용, 컨텐츠 내용, 컨텐츠 내용, 컨텐츠 내용 무수히 많음";
    var contentsBytes = Encoding.UTF8.GetBytes(contents);
 
    //Temp 파일 얻어 오기
    var pathInfo = Path.GetTempFileName();
    using (var fileStream = File.OpenWrite(pathInfo))
    {
        // 파일에 쓰기
        fileStream.Write(contentsBytes0, (int)contentsBytes.Length);
        fileStream.Flush();
        fileStream.Close();
    }
}


비 동기식 파일 저장 예제

[TestMethod]
public void AsyncUnitTest_APM_ASync()
{
    var contents = "컨텐츠 내용, 컨텐츠 내용, 컨텐츠 내용, 컨텐츠 내용, 컨텐츠 내용 무수히 많음";
    var contentsBytes = Encoding.UTF8.GetBytes(contents);
 
    //Temp 파일 얻어 오기
    var pathInfo = Path.GetTempFileName();
    var fileStream = File.OpenWrite(pathInfo);
 
    // 파일에 쓰기
    var result = fileStream.BeginWrite(contentsBytes0contentsBytes.Lengthnew AsyncCallback(FileStreamReadEnd), fileStream);
    var handle = result.AsyncWaitHandle;
 
    // 완료 될 때까지 대기, 옵션 사항으로 사용함.
    WaitHandle.WaitAll(new[] { result.AsyncWaitHandle });
 
    //WaitHandle 사용으로 대기는 해당 쓰레드가 완료가 되며 아래 Flow에서 완료된 값이 필요 할 때 대기 할 수 있으나 필요치 않을 때는 대기 하지 않고 넘어 가도 됨
    #region WaitHandle에서 완료가 되면 실행 해야 하는 코드
    //code
    #endregion
}
 
public void FileStreamReadEnd(IAsyncResult result)
{
    //완료 여부 체크
    if (result.IsCompleted)
    {
        //넘겨준 파라메터를 해당 object로 변환
        var fileStream = (FileStream)result.AsyncState;
        //완료 표시
        fileStream.EndRead(result);
        fileStream.Flush();
        fileStream.Close();
    }
}

 일단 한번 Copy & Paste를 해서 디버깅 모드로 따라가면서 흐름을 익혀 보자.


동기식 실행 : 특별한 설명이 없어도 절차적으로 한번에 하나씩 순차적으로 흐름을 타고 내려가는 것을 확인 할 수 있을 것이다.

비 동기 실행 : BeginWrite를 실행하면 흐름이 두 가지로 나눠지는 것을 확인 할 수 있다.

                    BeginWrite를 실행하면 본 문맥에서는 result(IAsyncResult)객체를 반환하고 바로 아래 구문을 실행할 것이며 "WhatHandle"에서 IAsyncResult 객체가 완료가 될 때까지 대기하는 흐름이고 또 하나는 AsyncCallback에서 호출되어진 FileStreamReadEnd 메소드가 실행이 될 것이다. FileStreamReadEnd 메소드는 파일 내용을 다 읽었으면 EndRead ( 여기서는 FileStream의 BeginWrite의 쌍이다 )를 실행하여 IAsyncResult 객체에 완료가 되었다는 것을 알려 준다. 그리고 원래 하려고 하였던 Close 작업을 해 주고 완료 한다. 이와 같은 방식으로 파일 읽기를 비 동기로 작업 하였다.


 APM 타입 비동기 작업에서 중요한 객체는 IAyncResult다. 이 IAsyncResult 객체를 사용해서 비동기 진행 상태나 완료 상태를 알아 내고 Begin 할때 넘겨준 파라메터 객체를 이용해 특정 객체를 접근할 수 있다. 아래 그림1은 IAsyncResult의 전체적인 flow이다.


[그림1] IAsyncReslt 흐름도


 BeginMethod를 실행하면 IAsyncResult 돌려줘서 현재 문맥에서 컨트롤 할 수 있도로 하며(대기 또는 진행 여부 체크 등) AsyncCallbacak에서 정의해준 EndMethod를 실행할 때 파라메터로 IAsyncResult를 넘겨 준다. BeginMethod에서 반환된 IAsyncResult 객체와 EndMethod에서 받은 IAsyncResult 파라메터는 각은 객체다. 


 IAsyncResult 객체의 흐름과 BeginMethod, EndMethod의 상관관계를 알고 있으면 쉽게 비동기 실행할 수 있을 것이다. 아래는 IAsyncResult 객체의 맴버에 대한 설명이다. (MSDN 발췌)


멤버

설명

AsyncState

비동기 작업에 대한 정보를 포함하는 선택적 응용 프로그램 관련 개체입니다.

AsyncWaitHandle

비동기 작업이 완료될 때까지 응용 프로그램 실행을 블로킹하는 데 사용할 수 있는 WaitHandle입니다.

CompletedSynchronously

비동기 작업이 별도의 ThreadPool 스레드에서 완료되는 대신 BeginOperationName 호출에 사용된 스레드에서 완료되었는지 여부를 나타내는 값입니다.

IsCompleted

비동기 작업이 완료되었는지 여부를 나타내는 값입니다.


C# Rx

Reactive Extensions

http://msdn.microsoft.com/en-us/data/gg577609

http://msdn.microsoft.com/en-us/data/gg577610 (download)

http://introtorx.com

 

 

 얼마전 Rx가 2.0으로 release가 되었다. 일단 소개 영상을 한번 봐보시라.

http://channel9.msdn.com/Events/TechEd/Europe/2012/DEV413

http://channel9.msdn.com/Shows/Going+Deep/Bart-De-Smet-Rx-20-RC-Time-Error-Handling-SafeSubscribe-and-More


http://introtorx.com/Content/v1.0.10621.0/00_Foreword.html


http://blogs.msdn.com/b/rafaelf/archive/2013/03/18/various-implementations-of-rx.aspx


  • Rx .NET
  • RxJS (JavaScript)
  • Rx CPP
  • RxJava
  • Reactive Cocoa
  • RxPython
  • Reactive-Dart
  • Rx.rb (Ruby)

  •  

     어떠한가 기존 프로그램을 작성하는 패턴과 달라 보이지 않은가? Publishing하고 Subscription으로 프로그램의 흐름을 바꿔 보다 쉽고 확장 가능하도록 지원해 주고 있다. ( 같은 프로젝트에서 javascript용 라이브러리도 배포를 하고 있다 )

     

     이전 release에서는 Ix(Iterator extensions)이 별도의 프로젝트에서 진행이 되었으나 이번에는 두 프로젝트가 하나의 프로젝트로 합쳐져서 배포가 되고 있다는 것은 환영할 일이다. ( http://rx.codeplex.com/ )

    C# Asynchronous Programming Patterns

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

     

     요즘은 앱 전성 시대인거 같다. iOS를 기점으로 시작한 앱 개발 경쟁이 안드로이드와 Windows Phone으로 까지 번기게 되며 이제는 기존 어플리케이션도 앱과 같은 형식으로 배포를 하려하는 움직임까지 보이고 있다. 더군다나 이제는 MS Office 제품군에서도 내부 확장을 앱 형식으로 확장해서 사용할 수 있도록 지원해주고 있다. 예로 Outlook 2013에서 사용할 수 있는 앱을 개발하면 C/S와 Web에서 같은 앱을 사용할 수 있도록 지원해 주고 있다.

     

     이와 같이 앱 개발 전성시대와 함께 중요하게 대두된 것이 사용자 경험에서 중요한 변화가 시작이 되었다. 끊김없는 UI, 미려한 화면, 즉각적인 반응 등이 EndUser에게 선택되는 중요한 요점이 되었다. 프로그래머 입장에서는 비동기 프로그램 방식으로 위와 같은 형식으로 개발하여 사용자 경험에 대해 지원 해 줄 수 있다.

     

     .Net에서는 Async 개발 지원을 "Asynchronous Programming Pattern"을 통해 가이드를 해 주고 있다. 아래 리스트는 MS에서 가이드 해주는 패턴이다.

    - Asynchronous Programming Model (APM)

    Event-based Asynchronous Pattern (EAP)

    - Task-based Asynchronous Pattern (TAP)

     (http://channel9.msdn.com/Events/TechDays/Techdays-2012-the-Netherlands/2287)

     

    아래는 간단하게 패턴 유형 별 코드를 비교해 보도록 하겠다.

     

    Sync Pattern

    public class MyClassAsync
    {
        public int Read(byte[] bufferint offsetint count);
    }

     

    APM

    public class MyClassAPM
    {
        public IAsyncResult BeginRead(
            byte[] bufferint offsetint count,
            AsyncCallback callbackobject state);
        public int EndRead(IAsyncResult asyncResult);
    }

     

    EAP

    public class MyClassEAP
    {
        public void ReadAsync(byte[] bufferint offsetint count);
        public event ReadCompletedEventHandler ReadCompleted;
    }

     

    TAP - 추천 하는 방법 ( .Net framework 4.0 이상 지원 )

    public class MyClassTAP
    {
        public Task<int> ReadAsync(byte[] bufferint offsetint count);
    }

     

    여기에 추가적으로 .Net Framwork 에서는 async / await 패턴이 추가 되었다.

    public class MyClassAsyncAWait {     public async ReadAsync(byte[] bufferint offsetint count);  }

     

     이와 같이 .Net framework 에서는 비동기 프로그래밍에 대해서 보다 쉽고, 개발자의 수고를 덜 수 있도록 점점 진화된 비동기 프로그래밍 개발을 지원해 주고 있다.

     

    다음 포스트 각 패턴에 따르는 예제 코드를 하나 하나씩 짚어 보도록 하겠다.

     x64 운영체제에서 어플리케이션을 개발하다가 보면 OOM(OutOfMemory)이 발생하지 않을것으로 예상했었는데 실질적으로는 발생 하는 케이스가 있었습니다.


     실예로 대용량으로 DataSet들을 미들티어단에서 모아서 리턴해주는 Broker 기능을 하는 프로세스에서 발생을 하였습니다. ( 여러 서비스에서 리턴된 DataSet이 한곳에 모여져서 관리가 되다 보니 하나의 객체가 2Gb가 넘는 순간이 간혹 발생을 하여 한동안 골머리를 앓고 있었죠)


     이와 같은 문제점을 .Net 4.5에서는 쉽게 해결할 수 있게 되었습니다. 바로 런타임 설정 파일에 적용하면 하나 객체가 2Gb이상 관리할 수 있게 할 수 있습니다.


    http://msdn.microsoft.com/ko-kr/library/hh285054.aspx


    <configuration>

    <runtime> <gcAllowVeryLargeObjects enabled="true" /> </runtime> </configuration>


    위와 같이 설정하여 64bit 상에서 메모리 사용을 효율적이고 효과적으로 사용할 수 있도록 개발할 수 있습니다.

    + Recent posts