본문 바로가기

Programing/닷넷

[C#] 크로스 스레드 작업이 잘못되었습니다

대상: Visual C# > Windows > Windows Forms 응용 프로그램

주제: 비동기 처리

키워드: 델리게이트, 이벤트, 멀티 스레드




Windows Forms 응용 프로그램로 UI가 있는 애플리케이션을 만들었다.

파일을 읽고 처리를 해야해서 긴 작업을 사용자에게 피드백을 해주어야 해서 아래와 같이 프로그래스를 만들어주었다.

(위의 에러 메시지에서 progressBarHashing 컨트롤이 바로 그것!)




파일 처리하는 부분은 스레드로 만들어서 프로그램이 작업을 하는 동안에도 블록 되지 않도록 처리했고,
델리게이트와 이벤트를 작성하여 호출하는 측에서 이벤트 핸들러를 연결했다.
아래는 사용하는 쪽의 이벤트 핸들러 함수이다.

void OnProgressEvent(object sender, ProgressEventArgs e)

{

    progressBarHashing.Value = e.Value;

}


그런데 실행을 해보니 아래와 같은 메시지가 나왔다.

처리되지 않은 'System.InvalidOperationException' 형식의 예외가 System.Windows.Forms.dll에서 발생했습니다.


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


크로스 스레드 작업 이 뭐지?

파일을 검색하여 처리해주는 부분은 별도의 스레드가 생성이되고, 윈도우 폼의 스레드는 별도이다.

아래 그림에서 주 스레드(연두색 마크)가 Main()함수에서 호출한 MainForm 스레드이고, 그 아래가 사용자가 생성한 스레드이다. (마우스 오버시 나타는 스택 정보중, ThreadStart()에 의해 호출된 사용자 메소드 ThreadTask()가 바로 그것)


인터넷을 찾아보니 무시하라고 하는 글이 다수 있었다.


나중에 찾은 것은 Invoke로 처리를 해주라고 되어 있었다.


결국 아래와 같은 코드가 되었다.

void OnProgressEvent(object sender, ProgressEventArgs e)

{

    // 해싱처리하는, 부분이 별도의 스레드이므로 Invoke처리

    if(progressBarHashing.InvokeRequired)

    {

        progressBarHashing.Invoke(new MethodInvoker(delegate()

        {

            progressBarHashing.Value = e.Value;

        }));

    } else {

        progressBarHashing.Value = e.Value;

    }

   

}}));

}


그런데 불편해!

코드도 길어지고, 꼭 사용자 측(라이브러리로 만든다면..)에서 불편하게 Invoke처리를 해주어야 하나 의문이 들었다.

그런데,, BackgroundWorker 클래스가 있었다. => http://msdn.microsoft.com/ko-kr/library/system.componentmodel.backgroundworker.aspx



어차피 반복적으로 적용되는 문제이기 때문에 유틸리티를 만들어놓으면 편할 것 같았다.

C#에서는 이미 만들어진 클래스에 메소드를 추가할 수 있는 방법이 있었다. 이른바 정적 메서드.

아래 샘플은 TextBox 윈폼에 대해 크로스 스레드 문제를 처리할 수 있게 해준다.

public static class CrossThreadWorkHelper

    {

        public static void AppendTextCrossThread(this TextBox textBox, string appendText)

        {

            if (textBox.InvokeRequired)

            {

                textBox.Invoke(new MethodInvoker(delegate()

                {

                    textBox.AppendText(appendText);

                }));

            }

            else

            {

                textBox.AppendText(appendText);

            }

        }

    }


사용방법은 그냥 AppencText 대신에 AppendTextCrossThread를 사용하면 된다.