posted by 방랑군 2012. 1. 17. 02:52

그것은 C# 프로그래밍의 첫걸음을 내디면서 만난 첫번째 난관이었다.

단지 쓰레드를 생성하고 그 안에서 폼에 붙어있는 레이블 컨트롤의 텍스트 속성만 바꿔주려 했을 뿐이었는데, 난데 없이 튀어나오는 예외메세지에 당황하지 않을 수 없었다. 원인을 찾아보니,

C# 스레드에선 다른 스레드의 컨트롤을 건드는 일은 하지 말아야 한다는 것이었다.

이래서 더더욱 난 C/C++이 좋을 수 밖에. C#은 너무 딱딱하다. 깐깐하다.

그럼 이런 상황을 가능하도록 만들려면? invoke를 통해 교차쓰레드접근을 우회하면 된다고 하는데,

무슨말인지, 나더러 어쩌라는건지 더더욱 당황스러워지고...인터넷 뒤져서 다른분들의 소스도 좀 찾아보고, 책도 보고, MSDN도 뒤져보고 나니 이렇게 하면 되는구나 하는 감이 오기는 하더라.

스포츠 뉴스에 보니, 퍼거슨이 나니를 팔일은 없을거라고 하던데...

어찌됬건간에 본론으로 돌아와서,

위의 예외창이 뜨는 경우의 예를 보면 다음과 같다.

public void threadfunc()

{

m_mainform.button1.Text = i.ToString();

}

보시는데로, 이 함수는 메인폼 버튼의 텍스트를 설정해주는 코드다.

디버그모드로 빌드할 경우, 위 함수를 쓰레드로 돌리면 여지없이 예외박스가 출현해 주신다. 릴리즈 모드로 돌리면 잘 돌아가는것 처럼 보이는데 영 찜찜하다. 나중에 문제가 될지도 모르겠고, 비록 돌긴 돌아도 하지 말라는 짓이니 만큼 예외박스를 없애야 속이 편할 것 같다.

예외를 발생시키는 위 함수를 다음과 같이 고쳐보면,

public void threadfunc()

{

m_mainform.Invoke(new MethodInvoker(delegate()
{
m_mainform.button2.Text = i_1.ToString();
}));

}

어라, 잘 돈다. 쓰레드에서 버튼 텍스트를 직접 변경하는게 아니라, 폼의 Invoke 메소드를 통해서 버튼의 텍스트를 변경한다.

그리고 Invoke메소드를 사용하기 위해선 delegate를 인자로 제공해 주어야 한다. 위 예에선 MethodInvoker delegate가 제공되었는데, Microsoft.JScript 네임스페이스 안의 MethodInvoker 클래스와 혼동하지 말아야 겠다.

MethodInvoker delegate는 인자로 제공된 메소드를 실행하는 delegate를 나타내준다.

여기서 MethodInvoker에 제공된 인자는 바로,

"delegate(){m_mainform.button2.Text = i_1.ToString();}" 요놈이다.

메소드가 통째로 들어가 있다.

보기엔 안좋지만, 결국엔 폼의 Invoke메소드에 delegate를 인자로 제공하여 원하던 것을 할 수 있게 되었다.

한가지 불편한 점은 MethodInvoker delegate는 오로지 파라미터도 없고 리턴값도 없는 메소드만을 인자로 사용할 수 있다는 점인데, delegate 메소드에 파라미터를 넘겨야 할 경우엔 어떡해야 하나?

머리좀 굴려보면 굳이 파라미터가 필요 없더라도 같은 효과를 얻을 수 있을 것이고,

꼭 파라미터를 넘기고 싶다면 넘기면 된다. 어떻게?

다음과 같이 매개변수를 받은 함수의 타입의 delegate를 선언해준다.

delegate void SetObjectText_invoke3(int i);

이 예에선 int형의 변수를 파라미터로 취하는 함수타입으로 delegate를 선언했다.

C에서의 함수포인터의 개념과 비슷해보인다.

타입을 선언하였으면 그 타입으로 변수를 정의한다.

SetObjectText_invoke3 MI_SetText;

그리고, MI_SetText에 우리가 실제로 사용할 함수를 대입해넣는다.

MI_SetText = settext_invoke3;

settext_invoke3함수는 아래처럼 정의되어있다.

public void settext_invoke3(int i)

{

m_mainform.button4.Text = i.ToString();

}

역시, 매개변수로 받는 i를 이용해 버튼컨트롤의 Text를 바꾸는 코드다.

이제 쓰레드 안에서 다음처럼 사용하면 된다.

m_mainform.Invoke(MI_SetText, i);

샘플첨부.

TEST_Delegate_Winform.zip