그러냐

스레드 간 컨트롤 접근과 스레드 동기화 1탄 본문

c#

스레드 간 컨트롤 접근과 스레드 동기화 1탄

관절분리 2016. 1. 28. 11:20
반응형

스레드 간 컨트롤 접근과 스레드 동기화

닷넷 프레임웍을 이용해서 멀티스레드 어플리케이션을 작성할 일이 종종 있다.

특히나 파일 다운로드/업로드 나 긴 데이터베이스 작업을 수행할때, 사용자는 "죽은거야?......모래시계만 나오고..죽은거네..싱..." 라고 인내심을 버리기 일쑤다... 이럴때 우리의 개발자들은 예뿐 상태진행 화면을 작업 중에 보여주어 사용자가 "아..잘 처리되고 있구나..." 라고 인식할 수 있도록 해주어야 할 것이다.....그래야 한다...그래야 프로젝트 종료가 된다...-_-!

이 상태처리 화면을 폼으로 작성하기만 한다고 해서 작업 처리 중에 상태처리 화면을 동시에 보여줄 수 있는것은 아니라는것은 삼척동자도 다 안다....그래서 우리는 System.Threading.Thread.Start()를 사용해 스레드를 하나 생성하여 생성된 스레드가 상태처리 화면을 보여주도록 한다.

경험을 가진 개발자로서 단언컨대, 이게 엔터프라이즈 어플리케이션 세계에서 대표적인 멀티스레드 어플리케이션에 대한 요구라 감히 말할 수 있다. 동의하지 않는 사람은 나에게 오재미(운동회 박 터트리기..)를 던져라으..... 그리고 저런 넘도 있구나 하고 그냥 넘겨라으...

그런데!!!! 윈 폼을 이용해 이러한 작업을 하려고 하면 만나게 되는 예외 메시지...(컴파일 시에는 에러가 안난다.)

Cross-Thread operation is not valid....
즉 스레드 간 작업은 허용되지 않는다... 풀어 말하면, 한 스레드 상에서 생성된 윈도우 컨트롤에 대해서, 다른 스레드가 접근할 수 없다는 것이다. 엄밀하게 말하면, 해당 윈도우 컨트롤에게 변경을 가하는 함수를 호출하거나 속성을 변경하는 작업은 할 수 없고, 단지 컨트롤에 대한 정보를 조회하는 함수호출이나 속성 읽기는 허용된다.

그럼 스레드 A에서 생성한 텍스트 박스 컨트롤에다가 스레드 B가 문자열을 표시하고 싶다면?....

이때 사용할 수 있는 하나의 방법이 System.Windows.Forms.Control 네임스페이스에 존재하는 public Object Invoke ( Delegate method ) 함수를 이용하는 것이다.

파라미터는 호출하고자 하는 함수에 대한 델리게이트, 함수의 포인터 되겠다.

샘플을 만들어 보자.

1. 부모 윈도우(폼)

public delegate void DelegateShowJobRate(string s);

public partial class Form1 : Form
{
//상태표시 스레드 객체 변수
System.Threading.Thread oProgressThread = null;
//상태표시 윈도우 객체 변수
Form2 progressForm = null;
// 상태 윈도우에서 호출하게 될 함수에 대한 델리게이트
public DelegateShowJobRate DelegateShowJobRateInstance = null;

public Form1()
{

// 실제 함수 ShowJobRate() 에 대한 델리게이트 생성
DelegateShowJobRateInstance = new DelegateShowJobRate(this.ShowJobRate);

InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{

// 상태 표시 스레드 시작
oProgressThread =
new System.Threading.Thread(new System.Threading.ThreadStart(WorkerThreadFunction));

oProgressThread.Start();
}

//상태 표시 스레디를 위한 시작 함수
private void WorkerThreadFunction()
{
// 상태표시 윈도우 생성
// 부모윈도우를 상태표시 윈도우에서 참조해야 하므로 참조 전달
progressForm = new Form2(this);
// 작업 시작
progressForm.DoJob();
}

// 상태 표시 스레드(윈도우)에 작업진행율 갱신 정보를 부모 윈도우에 표시하기 위해 호출할 함수
public void ShowJobRate(string rateinfo)
{
this.label1.Text = rateinfo;
}

}

2. 상태 표시 윈도우(폼)

public partial class Form2 : Form
{
private Form1 mainForm;
public Form2(Form1 parentForm)
{
InitializeComponent();
//부모 윈도우에 대한 참조 저장
this.mainForm = parentForm;
}

//실제 작업을 처리하는 함수
public void DoJob()
{
this.Show();
string result = string.Empty;

for (int i = 1; i <= 100; i++)
{
result = i.ToString() + "%";
//이 윈도우 상태표시 컨트롤 갱신
this.progressBar1.Value = i;

//부모윈도우의 ShowJobRate 함수 호출을 위해 부모 윈도우의 해당 델리게이트를 전달하여 호출.
this.mainForm.Invoke(this.mainForm.DelegateShowJobRateInstance, new object[] { result });

// 마우스 이동, 키보드 입력 등등의 다른 Windows 메시지(이벤트) 처리
Application.DoEvents();
// 조금 기다리기.
System.Threading.Thread.Sleep(100);
}
}
public void CloseMe()
{
this.Close();
}
}

Control.Invoke를 이용해 다른 스레드 소속 컨트롤에 대한 접근(호출)을 테스트 해보았다.
지금까지 설명했는데도 왜 굳이 this.mainForm.Invoke(this.mainForm.DelegateShowJobRateInstance, new object[] { result }); 를 사용해야 돼?..그냥 this.mainForm.ShowJobRate(result); 라고 하면 되잖아...
그렇게 하면 위에서도 언급한 에러가 나온다... 확인 해 보길 바란다.
상태 표시 스레드에서 부모윈도우 스레드 소속의 컨트롤 label1 에 접근하는 것이 허용되지 않기 때문이다. 당연히 반대도 마찬가지다.

그냥 끝내기가 조금 아숩다. 그래서 스레드를 종료하기 위한 방법을 말해 보자.
스레드로 생성된 함수가 제어가 끝나면 스레드는 자동으로 종료(stop) 되어지므로 위의 예제에서 상태표시 윈도우는 WorkerThreadFunction() 함수 호출이 종료되면 자동으로 종료되어 진다. 그러면서 상태표시 윈도우도 자동으로 Close 되었다.
그러나 객체 변수는 전역으로 선언했으므로 살아있다.
필자 궁금해서 정말 상태표시 윈도우가 Close되고 소멸되었는지 궁금해서 확인해 본 바,
명시적으로 Close()를 호출해 주지 않은 이상 스레드가 종료되었다고 해서 상태표시 윈도우가 소멸되진 않는다. 단지 화면에 표시만 없어질 뿐이다.....정말이다...progressForm.IsDiposed로 확인해 보시라...~


그래서 상태표시 윈도우가 일을 끝냈으면 이 사실을 부모윈도우에게 알려서 자신을 죽이도록 해보겠다.
상태표시 윈도우가 부모윈도우에게 자신의 작업종료를 알리는 방법은 위에서 한 바와 같이 부모 윈도우의 함수를 호출해 줌으로서 가능할 것이다. 그러나 이건 해 보았으므로 다른 방법을 굳이 써 보고자 한다. 그러자니...

스레드간 동기화 메카니즘에 대한 개념이 필요하게 된다.

한 프로세스 내의 여러 스레드가 존재하게 되면 이들이 공유해서 사용하는 객체가 존재할 수 있다.
공유된 객체는 정의하기에 따라 다양하며, 메모리 상이 변수 일 수도 있고, 파일일 수도 있고, 필요한지 모르겠지만, 데이터베이스 연결일 수도 있을 것이다.
중요한 것은 여러 스레드가 공유해 쓰되, 한번에 하나의 스레드만이 해당 공유자원에 대한 제어권을 가지도록 하여 복잡한 문제를 야기하지 않도록 하는 것이 스레드간 동기화의 목적이라 할 수 있다.
이 스레드간 동기화의 메카니즘으로서 소개되는 닷넷의 클래스는

□ WaitHandle : 공유된 자원에 배타적으로 접근하기 위해 대기하는 운영체제 특성적인 객체(Operating System-specific object)를 캡슐화 한 클래스. 어렵다...문 말인지... 여튼 공유된 자원에 접근하려고 하는 객체를 표현하는 클래스라고 이해하면 되겠다.
다른 동기화 수단을 제공하는 클래스의 부모 격이다. 아래의 Mutext, Semaphore는 이 클래스를 상속하게 된다.

□ Mutext
: 공유 자원에 한번에 하나의 스레드만 접근할 수 있도록 동기화 방법을 제공, thread identity 를 요구함(뮤텍스를 획득한 스레드 만이 해당 뮤텍스를 다시 해제(Release) 할 수 있다.)
- System Mutext : 시스템 내의 모든 프로세스에서 접근 가능한 이름을 가지는 뮤텍스
- Local Mutext : 한 프로세스 내의 스레드에 의해서만 접근가능한 뮤텍스

□ Semaphore : 여러 공유 자원이 포함된 공유자원의 풀에 대한 접근을 한정된 수의 스레드만가 동시에 접근하도록 하는 동기화 방법을 제공, thread identity 를 요구하지 않음
- System Semaphoret : 시스템 내의 모든 프로세스에서 접근 가능한 이름을 가지는 세마포어
- Local Semaphoret : 한 프로세스 내의 스레드에 의해서만 접근가능한 세마포어

□ Monitor : 공유된 객체(배타적 코드 영역)에 대해 한번에 하나의 스레드만 접근 가능하도록 하는 동기화 방법을 제공, 이건 우리가 흔티 lock 키워드를 통해 사용하는 동기화 수단이다. C#에서 lock 키워드를 사용해 해당 코드 영역이 한번에 하나의 스레드에 의해서만 실행하능하도록 하는 것은 Monitor 를 이용하는 것이다.

□ AutoResetEvent : 스레드 간에 신호를 서로 주고 받을 수 있게 하여 결국 스레드간 동기화 방법을 제공.

등등이 있다..각각에 대해 궁금하신 분은.......( 시간이 허락하면 다음에.........-_+)......한가지 분명한건 MSDN에 잘 나와있다.

출처 : http://www.waglwagl.net/

 
 
반응형