통합검색
· 마을서비스란?  · 포럼마을  · 일반마을  · 테마마을  · 마을랭킹  · 활동왕
· 덱스퍼트란?  · TECBOX   · PRSBOX   · 이용안내  
· DEXT제품군  · 솔루션베이  · S/W & ESD 컴포넌트
· 프로그램베이
· LiveSeminar  · LiveConference
데브피아 C# 포럼 마을에 오신 것을 환영합니다.
  마을등급 C#   이 마을은 포럼마을 입니다이 마을은 자유가입제 마을 입니다 마을소개 페이지로 이동 전입신청
마을촌장촌장 비사모 주민 12879 since 2006-12-28
우리마을 공지사항
질문&답변
강좌&팁
자유게시판
자료실
앨범
개인게시판
[마을 게시판]
구인/구직 게시판
건의 / 운영 제안
랑데브 게시판
칼럼 게시판
개발자 고충상담
Dev Talk
자유토론방
벼룩시장
재나미 우스개
구인/프로젝트 정보
사람인 채용 게시판
  고객지원 게시판
마이 데브피아
 나의 e-Money 내역
 활동왕 My Page
 스크랩한 게시글보기
 쪽지관리
 주소록관리

 강좌&팁
 c# 코딩 연습 - 크로스 스레드와 Control.Invoke  | WinForm Program 2009-05-07 오후 2:15:56
 cplkimth  cplkimth님께 메시지 보내기cplkimth님을 내 주소록에 추가합니다.cplkimth님의 개인게시판 가기 번호: 1723  / 읽음:30,224

명작 도서 More Effective C#: 50 Specific Ways to Improve Your C#를 읽다 보니 Control.Invoke를 캡슐화 한 ControlExtensions 이라는 클래스에 대한 이야기가 나왔습니다.

원서에는 멀티 스레딩이나 크로스 스레드 등에 대한 배경 설명 없이 달랑 몇 줄의 코드 정도만 제시되어 있는데 (이것이 바로 이 책의 매력 ?반어적 의미 아님- 입니다.),

여기에 앞 뒤 설명을 붙이고, 간단한 예제를 만들어 보았습니다.


 

매 1초 마다 현재 시각을 표시하는 간단한 시계를 만든다고 합시다.

보통은 System.Timers.Timer 나 System.Threading.Timer, 혹은 System.Windows.Forms.Timer 를 사용하겠지만, 여기서는 Thread.Sleep 메서드를 이용하여 매 1초 마다 시간을 표시하는 방법을 사용해 보겠습니다.

실행하면, 시작하자 마자 바로 화면이 먹통이 되어버리는 걸 알 수 있습니다.

Thread.Sleep 메서드가 현재 메서드(UI가 생성된 메서드)를 잡고 있는 이른바 UI 블로킹이 일어난 것인데요. 이를 해결하기 위해서는 이 부분을 별도의 스레드에서 실행하여야 합니다.

1초를 기다린 후 라벨에 시각을 표시하는 로직이 별도의 메서드(StartNewThread)로 빠졌고, 이 메서드는 이제 메인 스레드(UI 스레드)와는 별개의 스레드에서 실행됩니다.

실행을 해 봅시다. 디버깅하지 않고 시작(CTRL + F5)을 실행하면 (운이 좋다면) 문제 없이 시계가 동작하는 걸 볼 수 있지만, 디버깅 시작(F5)을 실행하면 label1.Text = DateTime.Now.ToString();

에서 InvalidOperationException가 발생합니다.

일명 크로스 스레드 예외라고 하는데요.

label1.Text = DateTime.Now.ToString(); 에서 라벨 컨트롤에 접근을 하려고 하는데, 문제는 이 코드가 실행되는 스레드가 라벨 컨트롤이 생성된 스레드(메인 스레드, UI 스레드)가 아니라는 것입니다.


 

Control.Invoke를 호출하는 방법과 BackgroundWorker를 사용하는 두 가지 방법이 있을텐데, 대부분의 경우에는 BackgroundWorker 가 좋은 선택이 될 것입니다.

스레드 간에 상태를 공유하고, 진행상황을 보고하고, 작업을 중지하는 등 스레드와 관련한 대부분의 작업이 이미 구현되어 있기 때문에 편리하게 사용할 수 있습니다.

다만 BackgroundWorker에는옥의 티랄까, 한 가지 알려진 버그가 있습니다.

이 포스트의 주제가 BackgroundWorker가 아니고, 또 BackgroundWorker에는 곁다리로 슬쩍 이야기할 수 있는 수준 이상의 논점이 많으니까, 이 포스트에서는 Control.Invoke에 대해서만 이야기하도록 하겠습니다.


 

Control.Invoke를 호출하여 크로스 스레드 문제를 해결하는 코드는 다음과 같습니다.

먼저 라벨의 InvokeRequired 를 체크하여 Invoke가 필요한지를, 즉 컨트롤에 접근하는 스레드와 컨트롤이 생성된 스레드가  다른 스레드인지를 체크합니다.

굳이 Invoke가 필요하지 않다면 (비록 미미하더라도) 비용이 드는 Invoke를 호출할 필요가 없을 것입니다.

Control.Invoke의 시그니처는 다음과 같습니다.

첫번째 매개변수가 추상 클래스인 Delegate입니다.

따라서 label1.Invoke(new Delegate(DisplayDateTime)); 과 같이 대리자 인스턴스를 생성할 수가 없습니다.

대신 DisplayDateTimeHandler 라는 대리자 형식을 정의한 후 이 인스턴스를 전달하여야 합니다.

이제 실행(디버깅)을 하여 보면 크로스 스레드 문제 없이 잘 동작합니다.


 

크로스 스레드 문제가 해결되었으니 여기서 포스트가 끝나야 할 것 같지만, 이 포스트를 서야겠다고 생각한 이유는 사실은 지금 부터 시작 합니다.

Control.Invoke를 호출하는 위 코드를 Action 대리자와 확장 메서드를 사용하여 필드에서 사용할 만한 라이브러리로 만들어 봅시다.


 

닷넷 프레임웍 2.0에 추가된 두 가지 제네릭 대리자를 사용하면 대부분의 경우에는 대리자를 작성할 필요가 없습니다.

Func과  Action 대리자가 그것인데요. Func은 반환값이 있지만 Action은 반환값이 없다는(void) 점 외에는 동일하며, 두 대리자 모두 매개변수가 0개 ~ 4개인 오버로드가 각각 준비되어 있습니다.

(정확하게 이야기하자면, 매개변수가 0개인 Action 대리자의 형은 void Action() 이므로 제네릭 대리자는 아닙니다.)

그래서 매개 변수가 4개가 넘지 않는 시그니처를 가지는 대리자는 이 두 대리자로 표현할 수가 있는 것입니다.

위 코드에서도 DisplayDateTimeHandler 대리자를 따로 정의하지 않고 제네릭 대리자를 사용할 수 있습니다.

반환값이 없고 매개변수도 없으니까, 제네릭이 아닌 Action 대리자를 사용하면 되겠습니다.


 

이번에는 확장 메서드를 사용하여 위 코드를 캡슐화 해봅시다.

ControlExtensions라는 static 클래스를 만들고 아래와 같은 확장 메서드를 추가합니다.

(여기서는 매개변수가 0개 ~ 1개인 오버로드만 보이는데, 2개 ~ 4개인 오버로드도 정의해두면 편리합니다.)

이제 StartNewThread 메서드는 아래와 같이 간단해 집니다.

 

람다식이나 익명 메서드를 이용하면 DisplayDateTime 메서드도 따로 정의할 필요가 없어 코드가 좀 더 간단해 집니다.


 

연습 삼아 코드를 약간 고쳐 봅시다.

DisplayDateTime 메서드를 매개 변수를 가지는 형태로 다음과 같이 수정합니다.

그렇다면 이제 라벨의 Invoke 메서드를 호출할 때 현재 시각을 매개 변수로 넘겨야 합니다.

ControlExtensions.InvokeIfNeeded 오버로드 중,

가 호출되는데, 이는 다시 control.Invoke(action, arg); 를 호출합니다.

Control.Invoke의 두 번째 매개변수는 params object[] 이기 때문에, action 대리자의 매개변수의 갯수가 형에 상관없이 호출이 가능합니다.


 

보너스 : 제가 알렸드렸다고 소문 내지는 마시고, More Effective C# 책이 보고 싶으신 분은 이 링크를 눌러 보세요.

[코멘트] 좋음
2009-05-07 16:20
 kocjun  kocjun님께 메시지 보내기kocjun님을 내 주소록에 추가합니다.kocjun님의 개인게시판 가기 
와우 좋은 정보에 ...보너스 까지 감사합니다.
저장 취소
[코멘트] 좋음
2009-05-11 17:32
 webbug  webbug님께 메시지 보내기webbug님을 내 주소록에 추가합니다.webbug님의 개인게시판 가기 
좋은 정보가 되었습니다.
감사합니다.
저장 취소
[코멘트] 좋음
2009-05-13 05:17
 hihanguk  hihanguk님께 메시지 보내기hihanguk님을 내 주소록에 추가합니다.hihanguk님의 개인게시판 가기 
킁킁 난 왜 control 까지 해볼 생각을 못했을까나 ^^;;;

잘 쓰겠습니다~ ^^
저장 취소
[코멘트] 부끄럼
2009-05-19 15:58
 keane  keane님께 메시지 보내기keane님을 내 주소록에 추가합니다.keane님의 개인게시판 가기 
오 감사합니다.
저장 취소
[코멘트] 좋음
2009-05-30 12:14
 mosaic  mosaic님께 메시지 보내기mosaic님을 내 주소록에 추가합니다.mosaic님의 개인게시판 가기 
좋은 정보 감사합니다.^^
저장 취소
[코멘트] 좋음
2009-06-16 11:19
 three11  three11님께 메시지 보내기three11님을 내 주소록에 추가합니다.three11님의 개인게시판 가기 
유용한 정보였습니다.
저장 취소
[코멘트] 좋음
2009-07-16 09:45
 firerap119  firerap119님께 메시지 보내기firerap119님을 내 주소록에 추가합니다.firerap119님의 개인게시판 가기 
정말 정말 유용한 정보 감사합니다.
저장 취소
[코멘트] 좋음
2009-07-24 18:10
 seunghyun1207  seunghyun1207님께 메시지 보내기seunghyun1207님을 내 주소록에 추가합니다.seunghyun1207님의 개인게시판 가기 
아직은 약간 어렵게 느껴지는 내요이었지만 크로스 쓰레드 해결할 수 있다라는 것만으로도 좋은 정보이네요..

감사합니다.
저장 취소
[코멘트] 좋음
2014-12-12 11:04
 pyonc  pyonc님께 메시지 보내기pyonc님을 내 주소록에 추가합니다.pyonc님의 개인게시판 가기 
감사합니다!!
저장 취소
코멘트쓰기
  좋음   놀람   궁금   화남   슬픔   최고   침묵   시무룩   부끄럼   난감
* 코멘트는 500자 이내(띄어쓰기 포함)로 적어주세요.
목록 보기   지금 보고 계시는 글을 회원님의 my Mblog >> 스크랩에 넣어두고 다음에 바로 보실 수 있습니다.  
회사소개  |   개인정보취급방침  |  제휴문의  |   광고문의  |   E-Mail 무단수집거부  |   고객지원  |   이용안내  |   세금계산서
사업자등록번호 안내: 220-81-90008 / 통신판매업신고번호 제 2017-서울구로-0055호 / 대표: 홍영준, 서민호
08390, 서울시 구로구 디지털로32길 30, 1211호 / TEL. 02_6719_6200 / FAX. 02-6499-1910
Copyright ⓒ (주) 데브피아. All rights reserved.