본문 바로가기

IT프로그래밍/C Sharp

[C#] 크로스 스레드 작업이 잘못되었습니다. 바로 해결하기!!!

 

c# 윈폼에서 작업을 하다가 한번쯤은 이런 에러를 만나셨거나, 혹은 만날 수 있습니다.

 

크로스 스레드 작업이 잘못되었습니다.
컨트롤이 자신이 만들어진 스레드가 아닌 스레드에서 액세스되었습니다.

 

이 에러는 디버깅모드에서만 나타납니다.

원인은 위 에러메시지에서 보여주듯이,

컨트롤이 자신이 만든 스레드가 아닌 다른 스레드에 의해서 호출이 된것입니다.

 

간단한 테스트로 위의 상황을 만들고, 어떻게 해결할 수 있는지도 알아보겠습니다.


구현

- 내용

 1. 버튼 1을 클릭하면 텍스트 박스의 내용을 바꾼다.

 2. 버튼 2를 클릭하면 텍스트 내용을 바꾸는 스레드를 생성하고, 실행(Start)한다.

 

- 디자인

 

- 코드

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        textBoxShow();
    }

    private void button2_Click(object sender, EventArgs e)
    {
        Thread thread = new Thread(new ThreadStart(textBoxShow));
        thread.Start();
    }

    private void textBoxShow()
    {
        richTextBox1.Text = "Button Clicked !!";
    }
}

 


 

 


테스트

디버그 모드에서 실행 후, button1을 클릭하면

Textbox의 문구가 바로 바뀌는 것을 알 수 있습니다.

하지만

button2를 클릭하면

다음 처럼 크로스 스레드 작업이 잘못되었습니다. 컨트롤이 자신이 만들어진 스레드가 아닌 스레드에서 액세스되었습니다. 라는 에러를 뿜습니다.

원인은 위에서도 언급했듯이, 또 VS에서 보여주듯이, 컨트롤이 자신이 만들어진 스레드가 아닌 스레드에서 호출되었기 때문입니다.

 

조금 풀어본다면

Winform에서 폼, 즉 화면에 표출되는 부분도 스레드에 의해 구동이 되고 있습니다.(윈폼에서 이 스레드를 메인스레드라고 부릅니다)

버튼을 눌렀을 때, 1억회의 반복을 실행하는 반복문을 만들고 실행시키면,(다른 쓰레드 없이)

그 1억회의 반복이 끝나기 전까지 다른 버튼이나, 텍스트 박스 등 컨트롤이 눌러지지도 않고 써지지도 않는 것을 경험해 볼 수 있습니다.

버튼을 눌렀거나, 키보드가 입력되었을 때, 그것을 처리해야하는 쓰레드가 1억회의 반복문을 실행하고 있기때문에

화면이 멈춰있는것처럼 보이는 것이죠..

이런것을 처리하기 위해 쓰레드가 사용됩니다.

 

화면을 뿌려주는 쓰레드는 화면을 뿌려주는걸 해야하니까,

다른 쓰레드를 만들어서 그 일을 하도록 하는 것이죠.

 

하지만 문제는!! 컨트롤은 자신이 만들어진 스레드가 아닌 스레드에 의해서 그 속성이나 값이 변경되면 에러를 뿜습니다.

 

위의 테스트 예제에서는

똑같은 textBoxShow()라는 메서드를 실행시켰을 때,

button1은 폼의 동작에 대해 반응하고 실행시키는 스레드가 해당 구문을 수행하고

button2는 new thread를 통해서 만들어진 새로운(다른) 스레드가 해당 구문을 수행합니다.

그리고 에러가 발생하는 것이죠.


 

 

 

 


해결법

위에서 발생한 크로스 스레드 에러를 해결하기 위한 방법은 2가지 입니다.

방법 1

public Form1()
{
    InitializeComponent();
    CheckForIllegalCrossThreadCalls = false;
}

클래스의 생성자 혹은 다른 메서드에

CheckForIllegalCrossThreadCalls = false;

를 선언해줍니다.

 

CheckForIllegalCrossThreadCalls란

애플리케이션이 디버깅되는 동안 컨트롤의 핸들속성에 액세스하는 잘못된 스레드에 대한 호출을

catch할지를 나타내는 값입니다.

즉, 크로스 스레드 에러가 발생해도 표출시키지 마!! 라고 선언한 것입니다.

 

실제로 해당 구문을 추가하고 디버그모드로 실행을 해보면 에러없이 코드가 수행되는 것을 확인 할 수 있습니다.

크로스 스레드 작업 에러가 발생하지 않음

 

이 방법은 간단하지만, 추천하지 않습니다.

라고 해놓은 겁니다.

 

방법 2

private void textBoxShow()
{
    if(richTextBox1.InvokeRequired)
    {
        richTextBox1.Invoke(new MethodInvoker(delegate { richTextBox1.Text = "Button Clicked !!"; }));
    }
    else
        richTextBox1.Text = "Button Clicked !!";

}

우선 InvokeRequired를 통해서 해당 컨트롤이 Invoke를 해야할 지 판단합니다.

InvokeRequired란?

호출자가 컨트롤이 만들어진 스레드와 다른 스레드에 있기 때문에 메서드를 통해 컨트롤을 호출하는 경우 해당 호출자가 호출 메서드를 호출해야 하는지에 대한 여부 입니다.

true로 반환된다면 다른 스레드가 해당 컨트롤을 호출한 것 입니다.(크로스 스레드 에러가 발생하는 상황)

그런 상황일 때, Invoke를 통해서 컨트롤의 값을 변경 시켜야 합니다.

예제의 richTextBox뿐만 아니라 모든 컨트롤러들은 Invoke 함수를 갖고 있습니다.

Invoke란?

컨트롤의 내부 창 핸들이 있는 스레드에서 지정된 대리자를 실행하는 기능을 합니다.

 

즉 해당 스레드에서 직접 컨트롤의 내용을 변경시키는 것이 아니라,

해당 컨트롤의 스레드에 이걸 변경시켜 달라고 요청하는 역할을 수행합니다.

 

이것도 실행시켜서 결과를 확인해봅시다.

디버깅모드에서 크로스 스레드 작업 에러 없이 동작하였습니다.



결론

크로스 스레드 작업이 잘못되었습니다.
컨트롤이 자신이 만들어진 스레드가 아닌 스레드에서 액세스되었습니다.

라는 에러가 디버깅 중 발생하게 되었다면

 

CheckForIllegalCrossThreadCalls = false;

혹은

Invoke를 사용해서 해결 할 수 있고,

 

CheckForIllegalCrossThreadCalls을 사용하는 방법 보다 Invoke를 통해서 해결하는 방법을 추천드립니다.