Open-Closed Principle

[C#] Cross Thread로인한 InvalidOperationException해결법 본문

Programming/C#

[C#] Cross Thread로인한 InvalidOperationException해결법

대박플머 2014. 6. 16. 15:20

C# Windows Forms 응용 프로그램을 만들다 보면 작업시간이 오래 걸려 프로그램이 응답없음에 빠지는 경우가 있다. 

그래서 C# Windows Forms에서는 성능 향상을 위해 다중 스레딩을 사용하곤한다. 

그런데 다중 스레드를 사용하다가 Thread가 종료 되는 시점에서 Main Thread에 있는 Control을 제어해야 할 경우가 있다. 이경우 InvalidOperationException라는 Exception이 발생하곤한다. 

이유는 정책 때문이다. 

기본적으로 Control은 Main Thread에서만 제어를 해야 한다. 경합 상태, 교착 상태 등의 다른 스레드 관련 버그가 발생할 수 있기 때문이다. 따라서 Control에 대한 액세스가 스레드로부터 안전한 방식으로 호출되거나 Main Thread에서만 제어해야 한다. 그래서 .NET Framework에서는 사용자가 Main Thread외의 기타 Thread로부터 안전하지 않은 방식으로 컨트롤에 액세스할 경우 이를 감지할 수 있고 감지 한다.


소스를 보면 더 이해하기가 쉬울 것 같다. 

윈폼을 만들어서 TextBox를 만든다. 

그리고 Load이벤트에서 이렇게 작성해보자. 

1
2
3
4
private void Form1_Load(object sender, EventArgs e){
    var thread = new Thread(UnSafeTextBox);
    thread.Start("Hello World!");
}

그리고 UnSafeTextBox 함수를 구현해보자. 

1
2
3
private void UnSafeTextBox(object msg){
    this.textBox1.Text = (string)msg;
}

이렇게 하고 나서 F5번을 눌러보자. 
그럼 아마 


이런 그지 같은 상황이 초래 될 것이다. 아마 독자는 "이 그지 같은건 또뭐야?? 난 문법도 틀리지 않았는데 왜 이래?" 라고 할 것이다. 

당신은 틀린게 없다. 그런데 한가지 모르는 것이 있다. 바로 Main Thread외의 다른 Thread에서는 Control를 호출하거나 속성을 변경 해서는 절대 안된다. 



그렇다면 난 꼭 외부 Thread에서 Control를 제어 하고 싶은 사람은 어떻게 해야 하느냐?

크게 두가지로 나뉜다. 

그냥 Release 버전으로 사용하면서 교착상태나, 경합 상태가 만들어지지 않기를 기도 바라는 것이다.  ㅋㅋㅋ

위의 방법을 사용하게 되면 디버깅을 하지 못하니 디버깅을 할 수 있게 Control.CheckForIllegalCrossThreadCalls 속성을 잘못된 스레드에 대한 호출이 catch되면 true이고, 그렇지 않으면 false로 하면 된다. 만약 false로 하게 되면 디버기을 자유롭게 할 수 있다. 적당한 위치에 잘 보이게 선언 해두면 되지 않을까 싶다. 

그리고 Thread 버그가 일어나지 안도록 프로그래밍적으로 안전장치를 하는 것이다. 

MS Windows Form 정책상 Main Thread 외에서 Control를 호출 할 수 없기 때문에 컨트롤이 속해 있는 Thread에서 함수를 Call 해주는 Control 함수가 있다. 

바로 Invoke이다. 

Control.Invoke 메서드에 대한 간략한 설명을 하도록 하자. 

1
2
3
4
public Object Invoke(
    Delegate method,
    params Object[] args
)

함수는 이렇게 생겼다. 매개변수가 없는 경우 그냥 Delegate만 매개변수로 적어주면 된다. 


본론으로 들어와서 Invoke를 통해 Control를 제어 해보자. 

1
2
3
4
5
private delegate void DelegateSetTextBox(object msg);
private void UnSafeTextBox(object text){
    DelegateSetTextBox d = (msg) => {textBox1.text = (string)msg;};
    this.textBox1.Invoke(d, new object[]{text});
}

이렇게 하면 크로스 스래드가 발생하지 않는다. 

이렇게 Main Thread 외에 Thread에서 컨트롤을 호출하려면 이렇게 해주면 된다. 


이방법 말고도 BackgroundWorker라는 클래스가 있다. 이녀석을 사용하게 되면 정해진 위치에서 자체 Thread를 호출하여 정해진 일을 수행한다. 

소스를 보면서 설명하자. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private BackgroundWorker _background = new BackgroundWorker(); // 선언
 
public Form1(){
    InitializeComponent();
    _background.RunWorkerCompleted += 
                                        new RunWorkerCompletedEventhandler(_background_RunWorkerCompleted);
}
 
void _background_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e){
    textBox1.Text = "Hello World!";
}
 
public void button1_Click(object sender, EventArgs e){
    _background.RunworkerAsync();
}

우선 BackgroundWorker 클래스를 선언한다.

그리고 적당한 위치에 RunWorkerCompleted 이벤트를 추가 한다. 

RunWokerCompleted 이벤트에 컨트롤 제어 부분을 넣는다. 

그리고 컨트롤이 제어 되야 할 시점에 _background.RunWorkerAsync();를 호출한다.

이렇게 하면 Cross Thread가 발생하지 않는다. 

아주 쉽다. 정책만 이해하고 간단하게 정책에 반하지 않는 코딩법만 알면 해결 되는 문제이다. 

이상 끝..............

'Programming > C#' 카테고리의 다른 글

[C#] Nullable 사용법  (0) 2014.06.19
[C#] 명시적 인터페이스(Interface) 구현  (0) 2014.06.17
[C#] 람다 표현식  (0) 2014.06.16
[C#] 내부 클래스  (1) 2014.06.16
[C#] 소멸자 Finalize  (0) 2014.06.13