UI 애플리케이션에서 시간이 걸리는 작업을 메인 스레드에서 실행하면 화면이 멈추는 문제가 발생합니다. BackgroundWorker는 이벤트 기반으로 백그라운드 작업, 진행률 보고, 취소를 간단히 처리할 수 있는 클래스로 WinForms/WPF에서 자주 사용됩니다.
1. BackgroundWorker란?
BackgroundWorker는 별도의 스레드에서 작업을 수행하고, 안전하게 UI 스레드로 진행률과 완료 결과를 전달합니다. 주요 구성 요소는 다음과 같습니다.
- DoWork: 실제 작업을 수행하는 이벤트입니다.
- ProgressChanged: 진행률을 UI에 반영하는 이벤트입니다.
- RunWorkerCompleted: 작업 완료/취소/오류를 처리하는 이벤트입니다.
- WorkerReportsProgress, WorkerSupportsCancellation: 진행률/취소 지원 여부를 설정하는 속성입니다.
2. 기본 사용 흐름
1) BackgroundWorker 생성 및 설정
2) 이벤트 핸들러 연결
3) RunWorkerAsync로 시작 (필요 시 인자 전달)
4) DoWork에서 작업 수행, ReportProgress/취소 체크
5) ProgressChanged/RunWorkerCompleted에서 UI 업데이트
3. WinForms 예제
아래 예제는 긴 루프 작업을 백그라운드로 수행하며 진행률 표시와 취소를 지원합니다. 폼에는 progressBar1, labelStatus, buttonStart, buttonCancel가 있다고 가정합니다.
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
public partial class MainForm : Form
{
private readonly BackgroundWorker _worker = new BackgroundWorker();
public MainForm()
{
InitializeComponent();
_worker.WorkerReportsProgress = true;
_worker.WorkerSupportsCancellation = true;
_worker.DoWork += Worker_DoWork;
_worker.ProgressChanged += Worker_ProgressChanged;
_worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
buttonStart.Click += (s, e) => StartWork();
buttonCancel.Click += (s, e) => CancelWork();
}
private void StartWork()
{
if (_worker.IsBusy)
{
MessageBox.Show("이미 작업이 실행 중입니다.");
return;
}
progressBar1.Value = 0;
labelStatus.Text = "작업 시작";
// 인자 전달이 필요하면 RunWorkerAsync(object arg) 사용
_worker.RunWorkerAsync(100); // 예: 100회 반복
}
private void CancelWork()
{
if (_worker.IsBusy)
{
_worker.CancelAsync();
labelStatus.Text = "취소 요청됨";
}
}
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
var worker = (BackgroundWorker)sender;
int iterations = (int)e.Argument;
for (int i = 1; i <= iterations; i++)
{
if (worker.CancellationPending)
{
e.Cancel = true;
return; // 즉시 종료
}
// 시간이 걸리는 작업 (예시)
Thread.Sleep(50);
// 진행률 보고 (0~100)
int percent = (int)((i / (double)iterations) * 100);
worker.ReportProgress(percent, $"{i}/{iterations} 처리 중...");
}
// 결과 전달 (필요 시)
e.Result = "모든 작업 완료";
}
private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
labelStatus.Text = e.UserState as string ?? $"진행률: {e.ProgressPercentage}%";
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
labelStatus.Text = "작업이 취소되었습니다.";
}
else if (e.Error != null)
{
labelStatus.Text = $"오류 발생: {e.Error.Message}";
MessageBox.Show(e.Error.ToString(), "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
labelStatus.Text = e.Result as string ?? "완료";
progressBar1.Value = 100;
}
}
}
4. 취소와 예외 처리 팁
- 취소: DoWork 내부에서 CancellationPending을 주기적으로 확인하고 e.Cancel = true로 표시합니다. CancelAsync는 취소 요청일 뿐이며, 실제 종료는 DoWork 루프에서 처리합니다.
- 예외: DoWork에서 던진 예외는 RunWorkerCompleted의 e.Error로 전달됩니다. UI 스레드에서 안전하게 메시지를 표시하거나 로깅합니다.
- 진행률: ReportProgress(percent, userState)로 추가 상태 메시지를 함께 전달하면 UI 갱신이 편리합니다.
5. WPF에서의 사용
WPF에서도 동일한 방식으로 BackgroundWorker를 사용할 수 있습니다. ProgressChanged와 RunWorkerCompleted는 자동으로 UI 스레드(Dispatcher)에서 실행되므로 DoWork에서 직접 UI 컨트롤을 건드리지 않고 이벤트에서 UI를 갱신합니다.
using System.ComponentModel;
using System.Windows;
public partial class MainWindow : Window
{
private readonly BackgroundWorker _worker = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
_worker.WorkerReportsProgress = true;
_worker.WorkerSupportsCancellation = true;
_worker.DoWork += (s, e) => { /* 긴 작업 수행 */ };
_worker.ProgressChanged += (s, e) => { /* UI 갱신 */ };
_worker.RunWorkerCompleted += (s, e) => { /* 완료 처리 */ };
}
}
6. 언제 BackgroundWorker를 선택할까?
- 기존 WinForms/WPF 코드베이스에서 빠르게 백그라운드 작업을 붙이고 싶을 때 유용합니다.
- 진행률/취소가 필요한 단순 작업에 적합합니다.
- 새로운 프로젝트에서는 async/await(Task, IProgress, CancellationToken) 사용이 더 권장되는 추세입니다. 다만 레거시 UI 코드와의 호환성이나 이벤트 기반 패턴을 유지해야 하는 경우 BackgroundWorker가 실용적입니다.
7. 체크리스트
- DoWork에서 UI 접근 금지, ProgressChanged/RunWorkerCompleted에서만 UI 갱신합니다.
- IsBusy 확인 후 RunWorkerAsync 호출합니다.
- WorkerReportsProgress/WorkerSupportsCancellation을 적절히 설정합니다.
- e.Argument/e.Result로 입력/출력 데이터를 전달합니다.
'C#' 카테고리의 다른 글
| C# 고정 크기 버퍼(fixed size buffer) 사용 (0) | 2026.04.22 |
|---|---|
| C# BenchmarkDotNet으로 성능 측정하기 (0) | 2026.04.22 |
| C# 호출자 정보 특성(Caller Info Attributes) 활용 (0) | 2026.04.21 |
| C# unsafe 코드와 포인터 사용하기 (0) | 2026.04.21 |
| C# 구조체(Struct) 심층 분석 (1) | 2026.04.21 |