서두는 제외하고, 바로 예를 들어 설명하도록 하겠다. (편의 상 반말로...)
A란 프로그램은 여러 개의 Thread가 돌고 있다. 그 중 Thread T1은 B란 프로그램과 소켓 통신을 위해 사용되는데, T1은 B에서 전송하는 메시지를 처리한다. 그리고 처리된 메시지는 바로 텍스트 박스 컨트롤에 표시된다.
프로그래머는 단순히, 다음과 같이 코드를 썼다.
ThreadFunc T1()
{
while(1){
....
// B가 보낸 메시지를 받는다.
int nMessage = GetSocketMessage();
// 메시지를 텍스트 박스에 표기한다.
txtBoxControl.Text = nMessage.ToString();
}
}
그랬더니 Cross Thread Problem이란 메시지가 뜨며, 프로그램이 제대로 수행될 수 없다고 나온다. 과연, Cross Thread Problem이란 무엇인가 (물론, 위의 코드가 정확히 Cross Thread Problem을 발생하는 것은 아니다.. 하지만 그럴 가능성을 내재하고 있는 코드라고 할 수 있다.)?!
MSDN에 따르면,
전문적인 프로그램은 응답성 있는 사용자 인터페이스를 유지하기 위해 worker thread들을 사용하게 된다(*hyperthreading 또는 Multiple CPU들의 최대화를 위해서..). 하지만, .NET에서 이 문제는 특별히 쉬운 문제가 아니다, 왜냐면, Windows Forms 컴포넌트들은 thread safe 하지 않기 때문이다(특별히 번역할 말이 없어서-_-.. Thread들에 대해 안전하지 않다 정도의 뉘앙스일까나..?).
.NET의 어떤 버젼에서는 thread들 내부에 GUI 컴포넌트의 함수를 호출하려고 할 경우, 예외를 발생시킬 수 있다. 하지만, 종종 내가 의도한 바와 다른 이상한 결과를 내는 것으로 끝날 수도 있다(예외는 발생 안했지만, 내가 의도한 결과와 다르게 나올 수 있다는 말이다).
해결 방법은?!
만약, 다른 thread로부터 GUI 컴포넌트를 호출하고 싶다면, 직접적으로(directly) 호출할 수는 없다. 대신에 컨트롤 클래스로부터 상속받은 invoke 함수를 통해 간접적으로(indirectly) 실행하고자 하는 컴포넌트의 함수를 호출할 수 있다.
다른 invoke 함수와는 달리, 컨트롤 클래스의 invoke 함수는 데이터 corruption을 피해 호출을 동기화할 수 있도록 보장해준다.
컨트롤의 invoke 함수를 호출하기 위해 필요한 것은 invoke 함수의 인자로써 호출하고자 하는 함수를 delegate로 작성해서 넘겨야 한다. 그리고 invoke 함수의 인자로 넘어가는 함수에 필요한 인자는 object 배열로 넘겨야 한다.
예를 통해서 살펴보자!
예제 프로그램은 다른 프로그램에서 소켓을 통해 날려주는 탭 컨트롤의 인덱스를 받아, 현재 프로그램의 탭을 변경하는(그러니까 탭 화면을 바꾸는) 기능을 포함하고 있다.
즉, 다른 프로그램에서 전송하는 메시지를 처리하는 함수가 Thread로 동작하고 있고, 이 Thread 내부에서 탭 컨트롤의 탭을 변경하는 상황이 발생하기 때문에, Cross Thread 문제가 발생하였다(참고로 말하면, 각 탭에도 탭에 어떠한 데이터를 표현하기 위한 다른 Thread들도 동작하고 있기 때문에, Cross Thread 문제가 매우 발생하기 쉬운 구조다.)
예제 코드를 보면!
우선 WaitMessage()란 함수는 다른 프로그램에서 Socket을 통해 전송하는 메시지를 처리하기 위한 Thread 함수다. 이 때 받아들이는 메시지는 현재 프로그램의 탭 컨트롤의 인덱스이다. 이 Thread 내부에서 탭 컨트롤의 탭을 변경하기 위해 바로, SelectTab() 함수를 호출할 경우, Cross Thread 문제로 인한 Exception이 발생한다.
DoWork()는 이러한 문제를 회피하기 위한 함수다. 자세한 내용은 아래 코드를 보면서 얘기하자.
....

예제에선 ChangeTab이란 delegate를 선언하고, DoWork() 함수에서 사용한다.
DoWork() 함수에선 탭 컨트롤의 InvokeRequired 속성을 체크해 true일 경우, Invoke() 함수를 사용한다.
앞서 선언한 delegate의 파라미터로 탭 컨트롤의 SelectTab() 함수를 넘기고, Invoke() 함수에는 delegate와 SelectTab() 함수에 필요한 인자(탭 인덱스)를 함께 파라미터로 넘긴다.


결론
지금까지 간략하게 Cross Thread Problem에 대해 기술해보았다. 보다 깊은 내용보다는 왜 문제가 발생하고, 어떻게 해결하느냐에 대한 기술만 해놓았기 때문에, 보다 자세한 내용을 알고 싶다면, MSDN을 참조하기 바란다.
덧, hyperthreading에 대한 보다 자세한 내용은 http://blog.naver.com/subellia1?Redirect=Log&logNo=100011680762의 article을 참조하기 바란다.
Posted by 노헝그리

