델리게이트를 이용한 (비 동기)실행
http://msdn.microsoft.com/ko-kr/library/2e08f6yc.aspx
http://msdn.microsoft.com/en-us/library/2e08f6yc(v=vs.110).aspx
http://msdn.microsoft.com/ko-kr/library/system.delegate.aspx
처음 델리게이트(delegate)를 알게 되었을 때 필자는 이걸 어디에 사용하지?, Super class(interface, abstract class)를 사용해서 한번에 실행 하는것과는 어떤 차이가 있는거지? 한번에 모아서 실행하지 않고 그냥 그때 그때 실행해도 상관 없지 않을까? 잘 모르니 나중에 나중에 나중에~~~
이렇게 미루다가 오랜 시간 후 event 선언시에 델리게이트를 이용한다고만 알고 있었습니다. 그렇지만 이번 포스트에서는 아주 유용하게 비 동기로 실행 할 수 있도록 도와주는 델리게이트를 알아 보고자 한다.
2013/02/12 - [.Net Framework] - [Delegate] 똑똑하게 써보자 델리게이트! - 참고 사항
동기 메소드를 비 동기 적으로 호출하여 실행 할 수 있도록 델리게이트를 사용해 구현 할 수 있다. 구현 방법은 비 동기로 실행할 메소드의 Argument와 동일한 파라메터를 가지는 델리게이트를 선언하고 BeginInvoke를 통해서 비 동기적으로 실행 하도록 한다. 아래 코드를 보면 쉽게 이해할 수 있을 것이다.
//델리게이트 선언 public delegate string AasyncDelegateCaller(int callDuration, out int threadId);
[코드1] 델리게이트 선언
public string AasyncDelegate(int callDuration, out int threadId)
[코드2] 비 동기로 실행을 원하는 함수
"코드1"에서 델리게이트를 선언하고 "코드2"에서 비 동길로 실행을 원하는 메소드이다. 그렇다면 이제 이 둘을 연결하여 사용하는 부분을 보도록 하자.
// 델리게이트 인스턴스 생성 AasyncDelegateCaller caller = new AasyncDelegateCaller(AasyncDelegate); // 비 동기 시작 IAsyncResult result = caller.BeginInvoke(3000, out threadId, null, null);
[코드3] 델리게이트를 통해서 비 동기 실행
선언된 델리게이트를 인스턴스화 하고 파라메터를 비 동기 실행할 메소드를 입력해 줌으로써 사용할 준비가 된 것이다. 그렇다면 이제 BeginInvoke를 통해서 실행하면 메인 쓰레드와는 별개의 다른 쓰레드에서 실행되는 것을 확인 할 수 있을 것이다. 참고로 Invoke로 실행하면 기존과 같이 메인 쓰레드에서 동기적으로 실행이 될 것이다. 이제 전체 코드를 통해서 직접 확인을 해 보기를 바란다.
//델리게이트 선언 public delegate string AasyncDelegateCaller(int callDuration, out int threadId); static void Main(string[] args) { Program p = new Program(); // 비 동기 실행에서 쓰레드 번호를 알아 내는데 사용 함. int threadId; // 델리게이트 인스턴스 생성 AasyncDelegateCaller caller = new AasyncDelegateCaller(p.AasyncDelegate); // 비 동기 시작 IAsyncResult result = caller.BeginInvoke(3000, out threadId, null, null); //비 동기 메소드에 제어권이 넘어 가도록 설정 함. //Thread.Sleep(0); //일반적인 상황에서는 필요 없음. 제어권이 넘어가지 않으면 실행
Console.WriteLine("메인 쓰레드 {0} 에서 작업.", Thread.CurrentThread.ManagedThreadId); // EndInvoke를 통해서 비 동기 실행이 완료 될 때까지 대기 함. string returnValue = caller.EndInvoke(out threadId, result); Console.WriteLine("쓰레드 {0} 에서 작업, 반환 값 : \"{1}\".", threadId, returnValue); Console.WriteLine("완료"); Console.ReadKey(); } public string AasyncDelegate(int callDuration, out int threadId) { Console.WriteLine("테스트 메소드 시작"); Thread.Sleep(callDuration); threadId = Thread.CurrentThread.ManagedThreadId; return String.Format("실행 시간 {0} ms.", callDuration.ToString()); }
[코드4] 비 동기 실행 예제 코드
BeginInvoke로 실행하면 언제나 EndInvoke를 실행해 줘야 한다. EndInvoke가 해주는 역할은 비 동기 실행이 완료가 될때까지 대기하도록 해 주며 IAsyncResult의 WaitHandler에 완료 상태가 되도록 해준다. 이제 코드를 실행해 보면 아래 "그림1"과 같은 결과를 확인할 수 있을 것이다. ( 이 예제에서는 IAsyncResult를 통해서 대기하도록 하는 코드는 제외되었고 Async 관련 포스트에서 곤련 정보를 얻을 수 있을 것이므로 생략 하도록 하겠다 )
[그림1] 비 동기 실행 결과 화면
메인 쓰레드 ID는 1번이고 비 동기로 실행되는 쓰레드 ID는 3번을 가리키고 있다.
지금까지 간단하게 델리게이트를 이용해서 비 동기로 실행하는 방법에 대해서 알아 보았다. 그렇지만 이 케이스는 우리가 진짜로 원하는 방식의 비 동기 실행이라고 할 수 없다. 이 코드는 결과 적으로 메인 쓰레드에서 대기 하는 코드인 EndInvoke(IAsyncResult의 AsyncWaitHandle.WaitOne())가 메인 쓰레드에서 대기토록 하게 되어 있다. 만약 UI를 가진 응용 프로그램에서 위와 같은 구조를 가진다면 비 동기로 실행하는 이점이 없는 것이다. 이제 EndInvoke를 별도의 쓰레드에서 실행 되도록 구조를 변경해 보도록 하겠다.
public void AsyncDelegate_TestMethod2() { // 비 동기 실행에서 쓰레드 번호를 알아 내는데 사용 함. int threadId; // 델리게이트 인스턴스 생성 AasyncDelegateCaller caller = new AasyncDelegateCaller(AasyncDelegate2); // 비 동기 시작 IAsyncResult result = caller.BeginInvoke(3000, out threadId, new AsyncCallback(AsyncDelegateCallback), caller); Console.WriteLine("메인 쓰레드 {0} 에서 작업.", Thread.CurrentThread.ManagedThreadId); Console.WriteLine("완료"); } public string AasyncDelegate2(int callDuration, out int threadId) { Console.WriteLine("테스트 메소드 시작"); Thread.Sleep(callDuration); threadId = Thread.CurrentThread.ManagedThreadId; return String.Format("실행 시간 {0} ms.", callDuration.ToString()); } public void AsyncDelegateCallback(IAsyncResult result) { AasyncDelegateCaller caller = (AasyncDelegateCaller)result.AsyncState; // 비 동기 실행에서 쓰레드 번호를 알아 내는데 사용 함. int threadId; // EndInvoke를 통해서 비 동기 실행이 완료될때까지 대기 함. string returnValue = caller.EndInvoke(out threadId, result); Console.WriteLine("쓰레드 {0} 에서 작업, 반환 값 : \"{1}\".", threadId, returnValue); }
[코드5] 별도의 쓰레드에서 EndInvoke를 실행 함.
(중요 변경 사항을 " " 배경색으로 표기 하였다)
위 "코드5"와 같이 수정으로 메인 쓰레드에서 EndInvoke를 실행하지 않고 AsyncDelegateCallback 메소드( 별도의 쓰레드에서 호출 되어 실행 됨 )에서 실행이 되도록 하였다. 이런 구조로 변경이 되면서 얻을 수 있는 이점은 메인 쓰레드에서는 언제 비 동기 실행이 종료가 되는지 상관 없이 메인 쓰레드에서는 별도의 작업을 계속 진행하도록 할 수 있다. 관심의 분리를 통해 메소드 범위에서 집중할 수 있는 범위를 나눠줄 수가 있는 것이다. 이제 결과 화면을 확인해 보자.
[그림2] 비 동기 호출의 결과를 Callback에서 처리
결과 화면이 이전과는 약간 다르게 표시가 되엇다. 이 이유는 메인 쓰레드에서 Waiting(WaitOne(), EndInvoke()) 하는 작업 없이 메인 쓰레드는 계속 진행하도록 되어 있고 약 3초 후에 별도의 쓰레드에서 EndInvoke를 통해 비 동기 결과값을 화면에 뿌려주는 작업이 진행되기 때문에 위와 같은 결과로 완료가 된 것이다. "그림1"을 통해 쓰레드 실행 흐름을 보도록 하자
[그림3] 비 동기 실행
"그림3"에서처럼 메인 쓰레드에서 비 동기 쓰레드를 활성화 시키고 메인 쓰레드는 계속 진행하여 실행이 되고 있으며 활성화된 비 동기 쓰레드는 완료가 될 때가지 메인 쓰레드와는 별개로 실행이 된다. 이런 흐름에 의해서 "그림2"에 해당하는 화면이 결과로 뿌려지게 된 것이다.
참고로 델리게이트는 모든 .Net Framework 버전에서 지원되는 기능이 아니므로 아래 "표1"을 참고하도록 하자.
Delegate를 지원하는 프레임웍 정보
.NET Framework4.5, 4, 3.5, 3.0, 2.0, 1.1, 1.0에서 지원.NET Framework Client Profile4, 3.5 SP1에서 지원이식 가능한 클래스 라이브러리이식 가능한 클래스 라이브러리에서 지원Windows 스토어 앱용 .NETWindows 8에서 지원 |
[표1] 델리게이트 프레임웍 지원 정보
소스 코드 자체에 주석과 직관적인 코딩으로 충분히 파악이 가능할 것으로 예상하므로 별도의 설명을 생략하도록 하겠습니다. 포스트의 내용이 장황한 설명 보다는 주석과 소스코드 자체 만으로도 이해할 수 있도록 하기 위해 노력하였습니다.. 실제 개발에서도 필요한 소스는 단순히 Copy & Paste 만으로도 사용할 수 있습니다. 그리고 주석을 이용해 nDoc이나 별도의 자동 Document 제작 유틸로 API 문서를 만드는 데에도 도움이 되었으면 한다. ing™ |
'.Net Framework' 카테고리의 다른 글
[C# Thread] TaskFactory and TaskScheduler #2 - TAP (0) | 2013.03.14 |
---|---|
[C# Thread] TaskFactory and TaskScheduler #1 - TAP (0) | 2013.03.12 |
[C# Snippet]Task-based Asynchronous Pattern Task.Yield (0) | 2013.03.11 |
[C# Snippet]Task-based Asynchronous Pattern - ContinueWith (0) | 2013.03.08 |
[C# Snippet]Task-based Asynchronous Pattern Task.Run 메소드 (0) | 2013.03.08 |