C# - 작업자(Worker) 스레드와 UI 스레드
작업자 스레드는 보통 UI 요소를 갖지 않는 스레드를 일컫습니다. 따라서, 다음과 같은 경우의 스레드는 보통 작업자 스레드라고 합니다.
Thread thread = new Thread(threadFunc);
thread.Start();
private void threadFunc()
{
// 스레드 작업
}
이런 것도 작업자 스레드입니다.
ThreadPool.QueueUserWorkItem(
(arg) =>
{
// 스레드 작업
}, null);
반면 UI 스레드는 스레드에서 UI 요소를 생성해 사용하는 것을 말합니다. 그런데, UI 요소를 생성했다고 UI 스레드가 아닙니다. 그 UI 요소가 잘 동작하려면 메시지 펌프를 위한 메시지 루프 처리가 있어야 합니다. Win32에서는 다음과 같이 처리하던 코드입니다.
// https://learn.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// If threads are created without a message queue, why can I post to them immediately upon creation?
// ; https://devblogs.microsoft.com/oldnewthing/20241009-00/?p=110354
닷넷에서는 이러한 메시지 펌프를 Application.Run 함수에서 해줍니다. 따라서 다음의 스레드는 UI 스레드가 되는 것입니다.
Thread thread = new Thread(threadFunc);
thread.Start();
private void threadFunc()
{
Application.Run(...[생략]...);
}
물론, 스레드 풀에서 빌려온 스레드일지라도 메시지 루프를 가지면 UI 스레드가 됩니다.
ThreadPool.QueueUserWorkItem(
(arg) =>
{
Application.Run(...);
}, null);
실제로 테스트를 해볼까요? ^^ Form2 윈도우 코드를 다음과 같이 만듭니다.
using System;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form2 : Form
{
Timer _timer = new Timer(); // System.Windows.Forms.Timer는 타이머 처리를
// 내부적으로 윈도우 이벤트를 사용합니다.
public Form2()
{
InitializeComponent();
_timer.Interval = 1000;
_timer.Tick += Timer_Tick;
_timer.Start();
}
// 따라서, 아래의 함수는 이벤트 처리가 안되면 실행되지 않습니다.
private void Timer_Tick(object sender, EventArgs e)
{
this.textBox1.Text = DateTime.Now.ToString();
}
}
}
위의 윈도우를 다음과 같이 작업자 스레드에서 실행해 봅니다.
ThreadPool.QueueUserWorkItem(
(arg) =>
{
Form2 form = new Form2();
form.Show();
}, null);
그럼 윈도우까지는 뜨는데 텍스트 박스에 있는 시간이 업데이트가 안됩니다. 물론 그 외에도 WM_PAINT 같은 이벤트도 처리가 안되므로 화면이 마치 hang이 걸린 듯한 현상이 발생합니다.
반면, 다음과 같이 UI 스레드에서 실행하면,
ThreadPool.QueueUserWorkItem(
(arg) =>
{
Form2 form = new Form2();
Application.Run(form);
}, null);
이번에는 타이머 처리가 잘 되어 텍스트 박스가 1초마다 내용이 업데이트됩니다.
자, 그럼 여기서 윈도우 프로젝트에 기본적으로 생성되는 Program.cs를 한번 열어봅니다.
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
바로 저 코드! 이제는 이해가 되시겠죠? 바로 메시지 루프이며 덕분에 UI 스레드로써 기능을 하고 있는 것입니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]