본문 바로가기
📁 [4] 개발자 정보 & 코드 노트/C#

C# Windows Forms 강의 50편: 다중 쓰레드와 ProgressBar 활용

by wawManager 2025. 3. 25.

1. 강의 개요

이번 강의에서는 다중 쓰레드ProgressBar를 결합하여 긴 작업을 처리하면서 UI의 응답성을 유지하는 방법을 학습합니다.
특히, Thread 클래스와 Invoke 메서드를 사용해 UI를 안전하게 업데이트하고, 작업 진행 상태를 표시하는 애플리케이션을 구현합니다.


2. 학습 목표

  1. Thread 클래스를 사용해 긴 작업을 비동기로 실행.
  2. ProgressBar로 작업 진행 상태를 UI에 표시.
  3. UI 업데이트 시 발생할 수 있는 쓰레드 충돌 문제 해결.

3. 다중 쓰레드와 UI 업데이트

다중 쓰레드 개념

  • UI 쓰레드: 폼과 컨트롤의 이벤트를 처리하는 주 쓰레드.
  • 백그라운드 쓰레드: UI와 독립적으로 실행되는 작업 쓰레드.

Invoke 메서드

  • UI 컨트롤은 UI 쓰레드에서만 업데이트 가능.
  • 백그라운드 쓰레드에서 UI를 안전하게 업데이트하려면 Control.Invoke 메서드를 사용해야 함.

4. 실습: 다중 쓰레드로 작업 처리 및 ProgressBar 업데이트

요구사항

  1. Thread 클래스를 사용해 긴 작업(파일 처리 시뮬레이션) 비동기 실행.
  2. ProgressBar로 작업 진행 상태 표시.
  3. 작업 중에도 UI가 응답성을 유지.

폼 구성

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

ProgressBar progressBar1 (없음) 중앙 (300 x 30)
Button btnStart "작업 시작" 하단 왼쪽 (100 x 30)
Button btnCancel "작업 취소" 하단 오른쪽 (100 x 30)

코드 작성


1. Form1.cs

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

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        private Thread workerThread; // 작업을 실행할 쓰레드
        private bool cancelRequested; // 작업 취소 여부 확인 변수

        public Form1()
        {
            InitializeComponent();
            InitializeEvents();
        }

        private void InitializeEvents()
        {
            btnStart.Click += BtnStart_Click;
            btnCancel.Click += BtnCancel_Click;
        }

        // "작업 시작" 버튼 클릭 이벤트
        private void BtnStart_Click(object sender, EventArgs e)
        {
            if (workerThread == null || !workerThread.IsAlive)
            {
                cancelRequested = false; // 작업 취소 플래그 초기화
                progressBar1.Value = 0; // ProgressBar 초기화

                workerThread = new Thread(DoWork);
                workerThread.IsBackground = true; // 백그라운드 쓰레드 설정
                workerThread.Start();
            }
        }

        // "작업 취소" 버튼 클릭 이벤트
        private void BtnCancel_Click(object sender, EventArgs e)
        {
            if (workerThread != null && workerThread.IsAlive)
            {
                cancelRequested = true; // 작업 취소 요청
            }
        }

        // 긴 작업 처리 메서드 (백그라운드 쓰레드에서 실행)
        private void DoWork()
        {
            for (int i = 1; i <= 100; i++)
            {
                if (cancelRequested) // 작업 취소 요청 확인
                {
                    UpdateStatus("작업이 취소되었습니다.");
                    return;
                }

                Thread.Sleep(50); // 작업 시뮬레이션 (50ms 대기)
                UpdateProgress(i); // ProgressBar 업데이트
            }

            UpdateStatus("작업이 완료되었습니다!"); // 작업 완료 메시지
        }

        // ProgressBar 업데이트 (UI 쓰레드에서 실행)
        private void UpdateProgress(int value)
        {
            if (progressBar1.InvokeRequired)
            {
                progressBar1.Invoke(new Action(() => progressBar1.Value = value));
            }
            else
            {
                progressBar1.Value = value;
            }
        }

        // 작업 상태 메시지 업데이트 (UI 쓰레드에서 실행)
        private void UpdateStatus(string message)
        {
            if (InvokeRequired)
            {
                Invoke(new Action(() => MessageBox.Show(message, "상태")));
            }
            else
            {
                MessageBox.Show(message, "상태");
            }
        }
    }
}

2. Form1.Designer.cs

namespace WindowsFormsApp1
{
    partial class Form1
    {
        private System.ComponentModel.IContainer components = null;
        private ProgressBar progressBar1;
        private Button btnStart;
        private Button btnCancel;

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

        private void InitializeComponent()
        {
            this.progressBar1 = new ProgressBar();
            this.btnStart = new Button();
            this.btnCancel = 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(300, 30);
            this.progressBar1.TabIndex = 0;

            // btnStart
            this.btnStart.Location = new System.Drawing.Point(20, 70);
            this.btnStart.Name = "btnStart";
            this.btnStart.Size = new System.Drawing.Size(100, 30);
            this.btnStart.Text = "작업 시작";
            this.btnStart.UseVisualStyleBackColor = true;

            // btnCancel
            this.btnCancel.Location = new System.Drawing.Point(220, 70);
            this.btnCancel.Name = "btnCancel";
            this.btnCancel.Size = new System.Drawing.Size(100, 30);
            this.btnCancel.Text = "작업 취소";
            this.btnCancel.UseVisualStyleBackColor = true;

            // Form1
            this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 20F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(350, 120);
            this.Controls.Add(this.btnCancel);
            this.Controls.Add(this.btnStart);
            this.Controls.Add(this.progressBar1);
            this.Name = "Form1";
            this.Text = "다중 쓰레드 예제";
            this.ResumeLayout(false);
        }
    }
}

5. 실행 결과

  1. 초기 상태
    • ProgressBar는 비어 있고, "작업 시작"과 "작업 취소" 버튼이 표시됩니다.
  2. 작업 시작
    • "작업 시작" 버튼 클릭 → ProgressBar가 점차 채워지며 작업이 진행됩니다.
    • 작업 중에도 UI는 응답성을 유지합니다.
  3. 작업 완료
    • ProgressBar가 끝까지 채워지면 "작업이 완료되었습니다!" 메시지가 표시됩니다.
  4. 작업 취소
    • "작업 취소" 버튼 클릭 → 작업이 중단되고 "작업이 취소되었습니다." 메시지가 표시됩니다.

6. 주요 개념 요약

  1. Thread 클래스
    • 긴 작업을 백그라운드 쓰레드에서 실행하여 UI 스레드의 응답성을 유지.
  2. Invoke 메서드
    • UI 업데이트는 UI 스레드에서만 가능하므로 Invoke를 사용해 안전하게 호출.
  3. 작업 취소 처리
    • cancelRequested 플래그를 사용해 작업 취소 로직 구현.