본문 바로가기

Programing/닷넷

[콜백] System.Action<T>로 함수인자 구현하기 (AsyncWorker)

Node.js 때문에 자바스크립트 프로그램을 하고 났더니, 비동기 함수에 익숙해졌다.

혹시 비동기함수에 대해 이해가 안된단다면, 아래의 코드를 실행을 해보면 이해가 빠르겠다.

function foo() {

  console.log('before');

  setTimeout(function() {

    console.log('Something doing');

  }, 1000);

  console.log('after');

}


foo();


실행결과는 아래와 같다.


'Something doing'이 'after'가 찍히고 난 후에 찍힘을 알 수 있다.


사실 초첨은 코드의 굵은 글씨로 표시된 부분이다. C에서는 함수포인터라고 하고, 자바스크립트에서는 함수도 객체이기 때문에 자유자재로 인자로 넘기는 것이 자유롭니다.


C#에서는 어떻게 구현할 수 있을까?

실행하는 코드[Program.cs]는 아래와 같다.

class Program

{

    static void Main(string[] args)

    {

#if USE_SYNC_WORKER

        IWorker worker = new SyncWorker();

#else

        IWorker worker = new AsyncWorker();

#endif

        worker.DoWork(e => Console.WriteLine("Progress" + e));

        Console.WriteLine("Passed DoWork");

    }

}

DoWork 부분에 보면 람다식으로 수행할 코드를 인자로 넘김을 알 수 있다.

이 인자는 System.Action<T> 이라는 델리게이트 함수이다. T는 타입을 나타내는데 아래에는 하나의 int 타입의 인자를 받을 수 있다.  사실 .NET Framework 4.0에는 16개의 인자까지 구현이 되어 있는데 프레임워크에 따라서 다르다.

 프레임워크 버전

 최대 가능 인자 갯수

2.0

3.0

1

3.5

4

4.0

16

4.5

16


.NET Framework 4.5에서 오버로딩들.NET Framework 4.5에서 오버로딩들



[IWorker.cs] 코드

interface IWorker

{

    void DoWork(Action<int> callback);

}


우선 동기버전인 SyncWorker를 보겠다. 인자로 넘어온 Action<int>를 수행시에는 Invoke라는 메소드를 호출함을 알 수 있다.

class SyncWorker : IWorker

{

    public void DoWork(Action<int> callback)

    {

        for (int i = 1; i <= 10; i++)

        {

            callback.Invoke(i);

            Thread.Sleep(100);

        }

    }

}


비동기 AsyncWorker의 코드는 아래와 같다. 쓰레드를 사용해서 비동기 구현을 하였다.

닷넷 4.0이라면 async라는 키워드를 쓸 수 있지만 개발환경이 3.5다보니 쓰레드를 사용했다.

class AsyncWorker : IWorker

{

    public void DoWork(Action<int> callback)

    {

        var thread = new Thread(this.Work);

        thread.Start(callback);

    }


    private void Work(object data)

    {

        Action<int> callback = data as Action<int>;

        for (int i = 1; i <= 10; i++)

        {

            callback.Invoke(i);

            Thread.Sleep(100);

        }

    }

}


실행결과는 아래와 같다.


"Passed DoWork"문자열이 SyncWorker에서는 제일 마지막에 출력이 되었지만,

AsyncWorker에서는 제일 처음에 출력됨을 알 수 있다.


또 한가지 알 수 있는 것은, C#에서는 모든 스레드가 종료될 때까지 기다린다는 것이다.

Native C에서는 스레드를 만들면 자식 스레드가 동작중이라도 메인 스레드가 종료되면 프로세스가 종료되어 버린다. 그래서 자식 스레드 종료를 위해서는 별도의 조치(동기화 등)이 필요하다.


전체 소스코드(VS2008) : AsyncWorker.zip


추가참고사이트들:

 MSDN::Func<T1, T2, TResult> 대리자 : http://msdn.microsoft.com/ko-kr/library/bb534647(v=vs.110).aspx

 [C#강좌] 18.Func, Action :  http://www.sqler.com/394065