본문 바로가기
Study/C#

C# Windows Forms 강의 20편: Thread와 Task로 멀티스레딩 구현하기

by wawManager 2025. 2. 23.
728x90

1. 강의 개요

이번 강의에서는 Windows Forms에서 멀티스레딩을 구현하기 위한 ThreadTask를 학습합니다.
멀티스레딩은 긴 작업을 백그라운드에서 수행하고, UI가 멈추지 않도록 해줍니다.


2. 학습 목표

  1. Thread 클래스를 사용해 기본적인 멀티스레딩 구현.
  2. Task 클래스를 활용해 비동기 작업 처리.
  3. 멀티스레딩 작업 중 UI 업데이트 안전하게 처리.

3. Thread와 Task

Thread란?

Thread는 프로그램 내에서 작업을 동시에 실행할 수 있도록 하는 기본 단위입니다.

  • UI 스레드와 독립적으로 동작.
  • Thread.Start()로 실행.

Task란?

Task는 비동기 작업을 수행하는 더 높은 수준의 추상화입니다.

  • 작업을 정의하고, 백그라운드에서 실행.
  • Task.Run()으로 실행.

4. 멀티스레딩에서의 주의점

  1. UI 업데이트는 UI 스레드에서만 가능
    • UI 컨트롤을 업데이트하려면 Invoke 또는 BeginInvoke를 사용해야 합니다.
  2. 스레드 충돌 방지
    • 여러 스레드가 같은 데이터에 접근하지 않도록 조치해야 합니다.

5. 실습: Thread와 Task를 사용한 멀티스레딩

요구사항

  1. ProgressBar와 Label로 작업 진행 상태 표시.
  2. 버튼 클릭 시 Thread 또는 Task를 사용해 백그라운드 작업 실행.
  3. 작업 진행 중에도 UI가 멈추지 않도록 구현.

폼 구성

컨트롤 타입 이름 텍스트 위치 크기

ProgressBar progressBar1 (없음) 상단 (400 x 30)
Label lblStatus "상태: 대기 중" 중간 (400 x 30)
Button btnStartThread "Thread로 작업 시작" 하단 왼쪽 (150 x 30)
Button btnStartTask "Task로 작업 시작" 하단 오른쪽 (150 x 30)

코드 작성

Form1.cs

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // Thread를 사용한 작업
        private void BtnStartThread_Click(object sender, EventArgs e)
        {
            progressBar1.Value = 0;
            lblStatus.Text = "상태: Thread 작업 중...";

            Thread thread = new Thread(() =>
            {
                for (int i = 1; i <= 100; i++)
                {
                    Thread.Sleep(50); // 50ms 지연
                    UpdateUI(i); // UI 업데이트
                }

                UpdateUIStatus("상태: Thread 작업 완료");
            });

            thread.Start();
        }

        // Task를 사용한 작업
        private void BtnStartTask_Click(object sender, EventArgs e)
        {
            progressBar1.Value = 0;
            lblStatus.Text = "상태: Task 작업 중...";

            Task.Run(async () =>
            {
                for (int i = 1; i <= 100; i++)
                {
                    await Task.Delay(50); // 50ms 지연
                    UpdateUI(i); // UI 업데이트
                }

                UpdateUIStatus("상태: Task 작업 완료");
            });
        }

        // UI 업데이트 메서드 (스레드 안전)
        private void UpdateUI(int progress)
        {
            if (progressBar1.InvokeRequired)
            {
                progressBar1.Invoke(new Action(() =>
                {
                    progressBar1.Value = progress;
                }));
            }
            else
            {
                progressBar1.Value = progress;
            }
        }

        private void UpdateUIStatus(string status)
        {
            if (lblStatus.InvokeRequired)
            {
                lblStatus.Invoke(new Action(() =>
                {
                    lblStatus.Text = status;
                }));
            }
            else
            {
                lblStatus.Text = status;
            }
        }
    }
}

Form1.Designer.cs

namespace WindowsFormsApp1
{
    partial class Form1
    {
        private System.ComponentModel.IContainer components = null;
        private ProgressBar progressBar1;
        private Label lblStatus;
        private Button btnStartThread;
        private Button btnStartTask;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void InitializeComponent()
        {
            this.progressBar1 = new ProgressBar();
            this.lblStatus = new Label();
            this.btnStartThread = new Button();
            this.btnStartTask = new Button();
            this.SuspendLayout();

            // progressBar1
            this.progressBar1.Location = new System.Drawing.Point(20, 20);
            this.progressBar1.Name = "progressBar1";
            this.progressBar1.Size = new System.Drawing.Size(400, 30);
            this.progressBar1.TabIndex = 0;

            // lblStatus
            this.lblStatus.AutoSize = true;
            this.lblStatus.Location = new System.Drawing.Point(20, 60);
            this.lblStatus.Name = "lblStatus";
            this.lblStatus.Size = new System.Drawing.Size(120, 20);
            this.lblStatus.TabIndex = 1;
            this.lblStatus.Text = "상태: 대기 중";

            // btnStartThread
            this.btnStartThread.Location = new System.Drawing.Point(20, 100);
            this.btnStartThread.Name = "btnStartThread";
            this.btnStartThread.Size = new System.Drawing.Size(150, 30);
            this.btnStartThread.TabIndex = 2;
            this.btnStartThread.Text = "Thread로 작업 시작";
            this.btnStartThread.UseVisualStyleBackColor = true;
            this.btnStartThread.Click += new System.EventHandler(this.BtnStartThread_Click);

            // btnStartTask
            this.btnStartTask.Location = new System.Drawing.Point(200, 100);
            this.btnStartTask.Name = "btnStartTask";
            this.btnStartTask.Size = new System.Drawing.Size(150, 30);
            this.btnStartTask.TabIndex = 3;
            this.btnStartTask.Text = "Task로 작업 시작";
            this.btnStartTask.UseVisualStyleBackColor = true;
            this.btnStartTask.Click += new System.EventHandler(this.BtnStartTask_Click);

            // Form1
            this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 20F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(450, 200);
            this.Controls.Add(this.btnStartTask);
            this.Controls.Add(this.btnStartThread);
            this.Controls.Add(this.lblStatus);
            this.Controls.Add(this.progressBar1);
            this.Name = "Form1";
            this.Text = "멀티스레딩 예제";
            this.ResumeLayout(false);
            this.PerformLayout();
        }
    }
}

6. 실행 결과

  1. Thread 작업
    • "Thread로 작업 시작" 버튼 클릭 시, ProgressBar가 0에서 100까지 점진적으로 증가합니다.
    • 작업 중에도 UI는 반응하며, 작업 완료 후 상태가 업데이트됩니다.
  2. Task 작업
    • "Task로 작업 시작" 버튼 클릭 시, ProgressBar가 동일한 방식으로 업데이트됩니다.
    • 작업 완료 후 상태 메시지가 변경됩니다.

7. 주요 개념 요약

  1. Thread는 작업을 별도 스레드에서 실행하여 UI의 응답성을 유지.
  2. Task는 더 높은 수준의 비동기 작업 추상화를 제공하며, 비슷한 작업에 적합.
  3. UI 업데이트는 반드시 Invoke 또는 BeginInvoke를 사용해 스레드 안전하게 처리해야 함.

 

728x90