BackGroundWorker Blog 소스위치
https://github.com/cocoknight/myblog/tree/main/C%23/WorkerHandler/HelloWorker
동기/비동기는 여러 언어에서 다양한 방식으로 지원하고 있습니다.
동기,비동기 상세 개념보다는 먼저 C#에서 지원하는 비동기작업
핼퍼(Helper)클래스인 BackgroundWorker Class사용법을 알아
보도록 하겠습니다.
BackgroundWorker는 작업스레드와 UI스레드를 지원하며
시간이 오래 걸리는 일은 작업스레드에서 해당일의 진척 사항
또는 사용자 이벤트는 UI스레드에서 처리하도록 설계 되어
있습니다. 간단한 예제를 통해 BackgroundWorker Class사용법 및
사용유무에 따른 프로그램의 동작을 확인토록 하겠습니다.
Keyword
1. HelloWorker프로젝트 소개
2. BackgroundWorker Class 적용
3. BackgroundWorker Class 실행
4. BackgroundWorker Class사용 - 앱 응답시간 지연 없음
5. BackgroundWorker Class사용 하지 않음 - 앱 응답시간 지연 발생
HelloWorker 프로젝트 소개
HelloWorker는 BackgroundWorker Class의 적용 유무에 따른 앱의 반응
동작 및 BackGroundWorker동작을 Cancel할 수 있는 방법까지 확인할 수
있는 프로젝트 입니다.
BackgroundWorker Class 적용
BackgroundWorker는 비동기지원을 위한 C#의 사전지원 클래스이므로,
사용법은 아래와 같이 정형화 되어 있습니다. Worker객체 생성, 객체의
데이터 멤버에 DoWork,ProgressChanged,RunWorkerCompleted이벤트에
대한 이벤트핸들러 worker_DoWork,worker_ProgressChanged,
worker_RunWorkerCompleted을 등록합니다.
BackgroundWorker Class를 사용하기 위한 코드 적용은 완료 되었습니다.
File : CDeligentWorker.cs
using System.ComponentModel; //BackgroundWorker Namespace
using System.Diagnostics;
namespace HelloWorker
{
public BackgroundWorker worker; //BackgroundWorker class instance
public CProgressForm _progressform; //update진행 표시 목적
public CDiligentWorker()
{
Debug.WriteLine(string.Format("CDeviceManager Default Constructor"));
_progressform = new CProgressForm();
_progressform.Show();
//Declaration of Device Worker
worker = new BackgroundWorker(); //worker객체 생성
worker.WorkerReportsProgress = true; //진행율 업데이트 지원(true)
worker.WorkerSupportsCancellation = true; //비동기 취소 지원(true)
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
string argument = e.Argument as string;
Debug.WriteLine(string.Format("Worker Start:{0}",e.Argument));
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine(string.Format("Update UI Worker:{0}", e.ProgressPercentage));
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
System.Diagnostics.Debug.WriteLine(string.Format("Worker Complete:{0}", e.Cancelled));
}
}
BackgroundWorker Class 실행
DoWork이벤트 핸들러에서 필요한 작업들을 실행 하고,ProgressChanged 핸들러에서
화면 업데이트 작업을 진행합니다.DoWork수행이 종료되면 RunWorkerCompleted가
최종적으로 실행 됩니다.
다음은 "Start Worker"버튼을 눌러을 때 실행되는 동작 입니다.
File : Form1.cs
namespace HelloWorker
{
CDiligentWorker _worker;
//Start Work버튼 이벤트 핸들러
private void cmdStart_Click(object sender, EventArgs e)
{
string requestMode = "REPORT";
_worker = new CDiligentWorker();
_worker.worker.RunWorkerAsync(requestMode);
}
}
public void RunWorkerAsync ();
public void RunWorkerAsync (object? argument);
RunWorkerAsync메서드는 BackgroundWorker를 실행 합니다.
가능하면 인자를 전달할 수 있는 RunWorkerAsync(object? argument)형태를
사용하는 것을 권장 드립니다. argument는 필요에 따라 string,List,Dictionary등
다양한 데이터를 전달할 일이 분명히 생길 수 있기 때문 입니다.
지금은 string형 데이터를 전달하고 있습니다.
RunWorkerAsync메서드는 BackgroundWorker Class의 DoWork이벤트 핸들러인
worker_DoWork을 수행 시킵니다.
File : CDiligentWorker.cs
void worker_DoWork(object sender, DoWorkEventArgs e)
{
//전달된 인자 parsing
//string데이터가 전달 되었기 때문에 string형태로 파싱함.
string argument = e.Argument as string;
Debug.WriteLine(string.Format("Worker Start:{0}",e.Argument));
try
{
switch (argument)
{
case "REPORT":
{
worker.ReportProgress(20);
Thread.Sleep(1000);
Thread.Sleep(1000);
worker.ReportProgress(40);
Thread.Sleep(1000);
Thread.Sleep(1000);
worker.ReportProgress(60);
Thread.Sleep(1000);
Thread.Sleep(1000);
worker.ReportProgress(80);
Thread.Sleep(1000);
Thread.Sleep(1000);
worker.ReportProgress(100);
Thread.Sleep(1000);
Thread.Sleep(1000);
break;
}
default:
break;
}
}
catch (Exception ex)
{
Debug.WriteLine(string.Format("Full Stacktrace: {0}", ex.ToString()));
}
}
ReportProgress(Int32 percentProgress)
ReportProgress(Int32 percentProgress,object userState)
ReportProgress는 ProgressChanged Event을 발생시키며 두가지 타입이 있습니다.
여기서는 첫번째 타입을 사용했고, 단순히 진행사항(퍼센트)이외에 다른 데이터를
UI에 표시하고자 하면, 두 번째 타입를 사용하면 됩니다. 두번째 타입을 사용하는
형태는 추후 다시 확인토록 하겠습니다.
작업스레드에서 ReportProgress을 진행값 을 인자로 호출 하면, 해당 요청은 ProgressChanged 이벤트를 발생 시켜 worker_ProgressChanged 이벤트
핸들러를 수행 합니다.
File : CDiligentWorker.cs
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//This is UI Thread
//전달받은 인자 값(진행사항)을 파싱 해서 출력.
Debug.WriteLine(string.Format("Update UI Worker:{0}", e.ProgressPercentage));
try
{
System.Diagnostics.Debug.WriteLine(string.Format("Progress Value:{0}", e.ProgressPercentage));
_progressform.progressBar1.Value = e.ProgressPercentage;
_progressform.lblprogress.Text = e.ProgressPercentage.ToString() + "% completed";
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(string.Format("Full Stacktrace: {0}", ex.ToString()));
}
}
작업스레드(worker_DoWork)에서 ReportProgress을 호출할 때 실행이 되고,
진행사항 값을 파싱한 후, ProgrogressForm에 필요한 값을 전달하여 진행사항을
표시 한다. worker_ProgressChange메서드는 UI 스레드에서 실행됩니다.
작업스레드(worker_DoWork)가 코드 수행이 종료되면,RunWorkerCompleted이벤트가
발생되며,이벤트 핸들러인 worker_RunWorkerCompleted을 수행 한다.
File : CDiligentWorker.cs
void worker_DoWork(object sender, DoWorkEventArgs e)
{
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//This is UI Thread
System.Diagnostics.Debug.WriteLine(string.Format("Worker Complete:{0}", e.Cancelled));
try
{
//progressbar를 close시킨다.
_progressform.Close();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(string.Format("Full Stacktrace: {0}", ex.ToString()));
}
}
BackgroundWorker Class는 작업스레드,UI스레드가 비동기로 동작함으로코드 수행 및 외부이벤트(Hello Me버튼선택)발생시에도 응답시간 지연이 발생하지 않는다.
간단하게 프로그래스바가 완료될 때 까지 응답 처리에 지연이 생깁니다.
(거의 반응을 하지 않는다고 보시면 됩니다.)
BackgroudWorker Class를 사용해야 하는 이유!
.BackGroundWorker Class를 사용하지 않는 코드 예시
UI스레드에서 프로그래스바가 종료하기 전 까지는 버튼 클릭("Hello Me")
및 윈도우 폼 화면이동 등의 UI이벤트에 응답하지 않습니다.
File : Form1.cs
private void cmdNotWorker_Click(object sender, EventArgs e)
{
//Form에서 Worker를 사용하지 않고, ProgressBar를 진행하는 경우
try
{
_progressform = new CProgressForm();
_progressform.Show();
for (int i = 20; i <= 100; i += 20)
{
updateProgress(i);
}
_progressform.Close();
}
catch(Exception ex)
{
Debug.WriteLine(string.Format("Full Stacktrace: {0}", ex.ToString()));
}
}
void updateProgress(int level)
{
_progressform.progressBar1.Value = level;
_progressform.progressBar1.Refresh();
Thread.Sleep(1000);
_progressform.lblprogress.Text = level.ToString() + "% completed";
_progressform.lblprogress.Refresh();
Thread.Sleep(1000);
}