간단한 용어 정리
Invoke : 메인 쓰레드에서 동작하도록 해주는 기능 (다른 스레드 => 메인UI 스레드 동작 시켜줌)
InvokeRequired : Invoke가 필요한 상황인지 체크. (메인UI 스레드 != 현재 실행중인 스레드 체크)
Windows Form에서 UI는 메인 스레드에서만 변경이 가능하며 그 외의 접근에 대해서는 예외가 발생됩니다.
우선 사용 방법은 다음과 같습니다. 방법1 혹은 방법2를 참고하여 사용하면 됩니다.
(예외와 주의 사항은 더 아래쪽에 적어두겠습니다.)
using System;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form3 : Form
{
//방법2를 위한 delegate ******************
private delegate void SetLabelDelegate();
private SetLabelDelegate setLabelDelegate;
//****************************************
private string message1 = "";
private string message2 = "";
private string message3 = "";
public Form3()
{
InitializeComponent();
//방법2를 위한 delegate 등록 *************
setLabelDelegate += SetLabel;
//****************************************
}
private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(ChangeUI);
message1 = "mainThread " + AppDomain.GetCurrentThreadId().ToString();
thread.Start();
}
private void ChangeUI ()
{
message2 += "subThread " + AppDomain.GetCurrentThreadId().ToString();
if (InvokeRequired)
{
//방법1. MethodInvoker, delegate 조합
Invoke((MethodInvoker)delegate
{
message3 = "InvokeThread " + AppDomain.GetCurrentThreadId().ToString();
SetLabel();
});
//방법2. delegate 호출
//Invoke(setLabelDelegate);
}
else
{
SetLabel();
}
}
private void SetLabel ()
{
label1.Text = message1;
label2.Text = message2;
label3.Text = message3;
}
}
}
오류 예시.
[System.InvalidOperationException - Cross Thread]
: 메인 스레드가 아닌 다른 스레드에서 UI 변경을 한 경우 발생
using System;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form3 : Form
{
public Form3()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(ChangeUI);
MessageBox.Show(AppDomain.GetCurrentThreadId().ToString()); //main thread number
thread.Start();
}
private void ChangeUI ()
{
MessageBox.Show(AppDomain.GetCurrentThreadId().ToString()); //other thread number
label1.Text = "1";
}
}
}
주의 사항)
쓰레드 번호를 확인할 수 있도록 코드를 작성해놨습니다.
이것으로 현재 함수가 어떤 쓰레드에서 실행 중인지 볼 수 있는데,
Invoke를 통해 MainThread -> SubThread -> MainThread 방식의 흐름이 가능합니다.
반드시 필요한 곳에서만 Invoke를 사용해야 올바른 비동기 코드가 진행이 될 수 있습니다.
잘 못된 코드와 올바른 코드 예시를 남깁니다.
using System;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form3 : Form
{
public Form3()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Thread validInvokeThread = new Thread(ChangeUI);
validInvokeThread.Start();
//이렇게 호출되면 안됨.
//Thread invalidInvokeThread = new Thread(ChangeUI2);
//invalidInvokeThread.Start();
}
private void ChangeUI()
{
//스레드 동작과 메인UI 스레드가 원하는대로 잘 동작하게 됩니다.
SetLabel(label1, AppDomain.GetCurrentThreadId().ToString());
Thread.Sleep(1000);
SetLabel(label2, AppDomain.GetCurrentThreadId().ToString());
Thread.Sleep(1000);
SetLabel(label3, AppDomain.GetCurrentThreadId().ToString());
}
private void ChangeUI2()
{
//이렇게 사용하면 MainThread에서 모든 것을 처리해주게 됩니다.
//즉 "스레드의 사용 의미가 없다" 입니다.
if (InvokeRequired)
{
Invoke((MethodInvoker)delegate
{
ChangeUI2();
});
}
else
{
SetLabel(label1, AppDomain.GetCurrentThreadId().ToString());
Thread.Sleep(1000);
SetLabel(label2, AppDomain.GetCurrentThreadId().ToString());
Thread.Sleep(1000);
SetLabel(label3, AppDomain.GetCurrentThreadId().ToString());
}
}
private void SetLabel(Label label, string text) //delegate로 함수를 만들어서 처리도 가능
{
if (InvokeRequired)
{
Invoke((MethodInvoker)delegate
{
label4.Text = "메인스레드 : " + AppDomain.GetCurrentThreadId().ToString();
label.Text = text;
});
}
else
{
label4.Text = "메인스레드 : " + AppDomain.GetCurrentThreadId().ToString();
label.Text = text;
}
}
}
}
ChangeUI (UI는 메인에서, Sleep은 다른 스레드에서 처리)![]() |
ChangeUI2 (메인 스레드에서 처리. Sleep에 의해 멈추기도함)![]() |
'C# > Windows Form' 카테고리의 다른 글
[C# Windows Form] 섬네일 현재 영역만 그려주기 (Scroll, Thumbnail) (0) | 2024.11.08 |
---|---|
[C# Windows Form] GDI+ 이미지 돌리기 (돌아간 크기에 맞추어 이미지 크기 확장!) (cos, sin) (1) | 2024.06.03 |
[C# Windows Form] 둥근 버튼 만들기 (GraphicsPath) (0) | 2024.03.11 |
[C# Windows Form] 디자인 문서개요 (요소 계층 구조 표현) (0) | 2024.03.11 |
[C# Windows Form] 로그 클래스 (CallerMemberName) (0) | 2024.02.17 |