스레드 혹은 비동기 작업의 콜백에서 컨트롤에 접근하면 '크로스 스레드 작업이 잘못되었습니다' 라는 예외 메세지를 받아보신 적이 있으실겁니다. 이 예외는 컨트롤이 만들어진 스레드가 아닌 다른 스레드에서 접근을 했기 때문에 발생하는데요..
해결 방법은 너무나도 간단합니다.
원하는 작업을 하는 메서드를 만들고, 컨트롤의 스레드 내에서 실행시키게 하면 끝이죠.
저도 소켓을 이용해서 비동기 작업을 하다가 저 예외 메세지를 너무 많이 봤습니다.
예외가 발생해서, 만들던 프로그램의 진행 상태도 멈춰버렸기 때문에 최대한 빨리 해결해야 했는데, 이런 예외를 보는 사람이 국적없이 너도나도 참 많이 발생하나봅니다.
일단, 예외 발생과 어떻게 해결하는지를 보여드리기 위해서 아래와 같은 코드를 작성하였습니다.
(이 코드는 예외를 발생시킵니다)
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
namespace CrossThreadSafetyAccess {
public partial class MainForm : Form {
public MainForm() {
InitializeComponent();
}
private void Button1Click(object sender, EventArgs e) {
Thread t = new Thread(CrossThreadNonSafetyAccess);
t.Start();
}
private void CrossThreadNonSafetyAccess() {
try {
button1.Text = "Not safety code";
} catch (Exception ex) {
MessageBox.Show(ex.Message, ex.GetType().ToString(), MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
}
}
그리고 이 코드를 실행하고, 버튼을 클릭하면 이런 메세지 박스가 나오게 됩니다.
직접 스레드를 만들지 않고도, 이 예외가 발생하는 경우는 많습니다.
앞서 말씀드린 비동기 소켓의 콜백에서 접근했을 경우라던지, 스레드가 생성되어 독립적으로 처리되는 메서드에서의 접근이라던지..
그럼 이 예외는 어떻게 처리를 할까요?
처리 방법은 어렵지 않습니다.
먼저, 접근하려는 속성이나 메서드의 매개 변수와 반환 값을 잘 확인해서 그와 비슷한 대리자를 하나 만듭니다.
위의 코드의 경우 텍스트를 변경하는 것이기에 저는 대리자를 이렇게 만들었습니다.
delegate void CrossThreadSafetySetText(Control ctl, String text);
매개 변수로 컨트롤과 문자열을 받는데, 이는 컨트롤의 텍스트를 설정하려고 두 개의 매개 변수를 받은 것입니다.
그리고, 대리자를 만든 후에는 또 다른 메서드 하나를 만듭니다.
(이 메서드에서 실질적인 텍스트 설정이 이뤄지게 됩니다)
private void CSafeSetText(Control ctl, String text) {
/*
* InvokeRequired 속성 (Control.InvokeRequired, MSDN)
* 짧게 말해서, 이 컨트롤이 만들어진 스레드와 현재의 스레드가 달라서
* 컨트롤에서 스레드를 만들어야 하는지를 나타내는 속성입니다.
*
* InvokeRequired 속성의 값이 참이면, 컨트롤에서 스레드를 만들어 텍스트를 변경하고,
* 그렇지 않은 경우에는 그냥 변경해도 아무 오류가 없기 때문에 텍스트를 변경합니다.
*/
if ( ctl.InvokeRequired )
ctl.Invoke(new CrossThreadSafetySetText(CSafeSetText), ctl, text);
else
ctl.Text = text;
}
위의 메서드에선 CrossThreadSafetySetText 이란 대리자를 CSafeSetText 메서드를 넘겨줌으로써 초기화하고 있습니다.
이렇게 되면 이 함수는 이제 크로스 스레드에 안전한 코드가 됩니다.
그리고 이 함수의 내부는 이렇게 표현될 수 있습니다.
(* 흐름은 위에서 밑으로 가는 순차적인 구조입니다. 탭은 분기를 나타냅니다.)
CSafeSetText 메서드 호출
컨트롤의 InvokeRequired 속성
TRUE
컨트롤이 스레드를 생성하고, 생성된 스레드에서 CSafeSetText 메서드를 호출
CSafeSetText 메서드가 호출되면, 다시 위에서부터 내려오게 되지만 InvokeRequired 속성은 FALSE로 되기 때문에 바로 Text 속성에 접근하게 됨
FALSE
스레드 생성 없이 바로 Text 속성에 접근하여 텍스트 변경
그리고 최종적인 프로그램의 코드는 이렇습니다.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
namespace CrossThreadSafetyAccess {
public partial class MainForm : Form {
public MainForm() {
InitializeComponent();
}
delegate void CrossThreadSafetySetText(Control ctl, String text);
private void CSafeSetText(Control ctl, String text) {
/*
* InvokeRequired 속성 (Control.InvokeRequired, MSDN)
* 짧게 말해서, 이 컨트롤이 만들어진 스레드와 현재의 스레드가 달라서
* 컨트롤에서 스레드를 만들어야 하는지를 나타내는 속성입니다.
*
* InvokeRequired 속성의 값이 참이면, 컨트롤에서 스레드를 만들어 텍스트를 변경하고,
* 그렇지 않은 경우에는 그냥 변경해도 아무 오류가 없기 때문에 텍스트를 변경합니다.
*/
if ( ctl.InvokeRequired )
ctl.Invoke(new CrossThreadSafetySetText(CSafeSetText), ctl, text);
else
ctl.Text = text;
}
private void Button1Click(object sender, EventArgs e) {
CSafeSetText((Control)sender, "Cross Thread Safety Code");
}
private void Button2Click(object sender, EventArgs e) {
Thread t = new Thread(CNotSafetySetText);
t.Start();
}
private void CNotSafetySetText() {
button2.Text = "Cross Thread Not Safety Code";
}
}
}
button1 클릭시엔 크로스 스레드에 안전한 작업이므로 예외 없이 텍스트 설정이 수행됩니다.
하지만, button2 클릭시엔 크로스 스레드에 안전하지 않은 작업이므로 예외가 발생합니다.
이 글에 대한 예제 코드입니다.
이상으로 간단하게 '크로스 스레드 작업이 잘못되었습니다' 라고 나타나는 예외를 처리하는 방법을 설명드렸습니다.
텍스트 설정하는 것만이 아니라, 이 예제를 보시고 이해함으로써 이 방법을 응용해서 위치 변경, 배경색 변경 등에도 활용할 수 있음을 아셨으면 합니다!
긴 글 읽어주셔서 감사합니다!