안녕하세요. 클라인스입니다.
그동안 이것저것 일들로 포스팅을 하지 못하였네요..ㅎ^^
금일 알아볼 내용은 Thread클래스와 BackgroundWorker 클래스입니다.
이전 제가 블로그에 올린 http://blog.naver.com/clyne83/110037812556 글을 참고하시면 UI Thread에 대해서 알아보았고
Thread 사용시 UI 업데이트시 생기는 문제와 이유, 해결 방법을 알아보았습니다.
다시한번 요약해보면 UI 엘리먼트의 업데이트는 반드시 UI Thread내에서만 할 수 있으며 그 외의 스레드에서
UI를 업데이트 하고 싶을때는 Dispatcher라는 녀석을 통해 UI Thread에게 이것좀 업데이트 해줄래?라고 부탁하면 된다고 했습니다.
그러나 BackgroundWorker 클래스를 이용할 경우 Dispatcher를 얻어 BeginInvoke식으로 할 필요가 없습니다.
Thread클래스와 BackgroundWorker 클래스의 가장 큰 차이점?이라고 볼 수도 있겠는데요..
BackgroundWorker 클래스 속성 중에 WorkerReportsProgress 라는 것이 있습니다.
이것을 true로 세팅할 경우 스레드를 돌면서 ReportProgress 메소드를 호출하면 ProgressChanged 핸들러에 등록해 둔 이벤트 핸들러가
호출 되며 해당 핸들러에서 Dispatcher를 얻어 BeginInvoke이 아닌 직접 UI 엘리먼트를 업데이트 할 수 있습니다.
설명이 어렵죠?코드를 보면서 알아보겠습니다.
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace SL_BackgroundWorker_CS
{
public partial class Page : UserControl
{
private BackgroundWorker bw = new BackgroundWorker(); //BackgroundWorker클래스를 선언 및 할당
public Page()
{
InitializeComponent();
//ReportProgress메소드를 호출하기 위해서 반드시 true로 설정, false일 경우 ReportProgress메소드를 호출하면 exception 발생
bw.WorkerReportsProgress = true;
//스레드에서 취소 지원 여부
bw.WorkerSupportsCancellation = true;
//스레드가 run시에 호출되는 핸들러 등록
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
// ReportProgress메소드 호출시 호출되는 핸들러 등록
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
// 스레드 완료(종료)시 호출되는 핸들러 동록
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
private void buttonStart_Click(object sender, RoutedEventArgs e)
{
// 스레드가 Busy(즉, run)가 아니라면
if (bw.IsBusy != true)
{
// 스레드 작동!! 아래 함수 호출 시 위에서 bw.DoWork += new DoWorkEventHandler(bw_DoWork); 에 등록한 핸들러가
// 호출 됩니다.
bw.RunWorkerAsync();
}
}
private void buttonCancel_Click(object sender, RoutedEventArgs e)
{
// 해당 스레드가 스레드 취소를 지원한다면
if (bw.WorkerSupportsCancellation == true)
{
//스레드 취소(종료), 이 메소드를 호출하면 CancellationPending 속성이 true로 set됩니다.
bw.CancelAsync();
}
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; (i <= 10); i++)
{
//CancellationPending 속성이 true로 set되었다면(위에서 CancelAsync 메소드 호출 시 true로 set된다고 하였죠?
if ((worker.CancellationPending == true))
{
//루프를 break한다.(즉 스레드 run 핸들러를 벗어나겠죠)
e.Cancel = true;
break;
}
else
{
// 이곳에는 스레드에서 처리할 연산을 넣으시면 됩니다.
System.Threading.Thread.Sleep(500);
// 스레드 진행상태 보고 - 이 메소드를 호출 시 위에서
// bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged); 등록한 핸들러가 호출 됩니다.
worker.ReportProgress((i * 10));
}
}
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//바로 위에서 worker.ReportProgress((i * 10));호출 시
// bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged); 등록한 핸들러가 호출 된다고
// 하였는데요.. 이 부분에서는 기존 Thread에서 처럼 Dispatcher를 이용하지 않아도 됩니다.
// 즉 아래처럼!!사용이 가능합니다.
this.tbProgress.Text = (e.ProgressPercentage.ToString() + "%");
// 기존의 Thread클래스에서 아래와 같이 UI 엘리먼트를 갱신하려면
// Dispatcher.BeginInvoke(delegate()
// {
// this.tbProgress.Text = (e.ProgressPercentage.ToString() + "%");
// )};
//처럼 처리해야 할 것입니다. 그러나 바로 UI 엘리먼트를 업데이트 하고 있죠??
}
//스레드의 run함수가 종료될 경우 해당 핸들러가 호출됩니다.
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//스레드가 종료한 이유(사용자 취소, 완료, 에러)에 맞쳐 처리하면 됩니다.
if ((e.Cancelled == true))
{
this.tbProgress.Text = "Canceled!";
}
else if (!(e.Error == null))
{
this.tbProgress.Text = ("Error: " + e.Error.Message);
}
else
{
this.tbProgress.Text = "Done!";
}
}
}
}
소스를 통해서 좀더 자세히 알아보았는데요..사실 Thread클래스 이용시 취소에 관한 부분도 별도로 처리해야 하기 때문에
BackgroundWorker 클래스 사용이 편할 것 같네요..
그런데 주의할 점이 BackgroundWorker클래스가 제공하는 대로
BackgroundWroker클래스의 ReportProgress()메소드 호출 시 호출되는 핸들러에서 UI 엘리먼트를 갱신하는 것이 아닌 다른 부분에서
갱신시에는 Disptacher.BeginInvoke로 갱신해야 한다는 것 잊지마세요!!
유추해보면, 정확히 내부적으로 어찌 돌아가는지는 알 수 없지만, BackgroundWroker클래스에서 제공하는 방식으로 처리할 경우 BeginInvoke방식으로 처리한다고 짐작할 수 있겠죠?^^
즐은광~
그동안 이것저것 일들로 포스팅을 하지 못하였네요..ㅎ^^
금일 알아볼 내용은 Thread클래스와 BackgroundWorker 클래스입니다.
이전 제가 블로그에 올린 http://blog.naver.com/clyne83/110037812556 글을 참고하시면 UI Thread에 대해서 알아보았고
Thread 사용시 UI 업데이트시 생기는 문제와 이유, 해결 방법을 알아보았습니다.
다시한번 요약해보면 UI 엘리먼트의 업데이트는 반드시 UI Thread내에서만 할 수 있으며 그 외의 스레드에서
UI를 업데이트 하고 싶을때는 Dispatcher라는 녀석을 통해 UI Thread에게 이것좀 업데이트 해줄래?라고 부탁하면 된다고 했습니다.
그러나 BackgroundWorker 클래스를 이용할 경우 Dispatcher를 얻어 BeginInvoke식으로 할 필요가 없습니다.
Thread클래스와 BackgroundWorker 클래스의 가장 큰 차이점?이라고 볼 수도 있겠는데요..
BackgroundWorker 클래스 속성 중에 WorkerReportsProgress 라는 것이 있습니다.
이것을 true로 세팅할 경우 스레드를 돌면서 ReportProgress 메소드를 호출하면 ProgressChanged 핸들러에 등록해 둔 이벤트 핸들러가
호출 되며 해당 핸들러에서 Dispatcher를 얻어 BeginInvoke이 아닌 직접 UI 엘리먼트를 업데이트 할 수 있습니다.
설명이 어렵죠?코드를 보면서 알아보겠습니다.
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace SL_BackgroundWorker_CS
{
public partial class Page : UserControl
{
private BackgroundWorker bw = new BackgroundWorker(); //BackgroundWorker클래스를 선언 및 할당
public Page()
{
InitializeComponent();
//ReportProgress메소드를 호출하기 위해서 반드시 true로 설정, false일 경우 ReportProgress메소드를 호출하면 exception 발생
bw.WorkerReportsProgress = true;
//스레드에서 취소 지원 여부
bw.WorkerSupportsCancellation = true;
//스레드가 run시에 호출되는 핸들러 등록
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
// ReportProgress메소드 호출시 호출되는 핸들러 등록
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
// 스레드 완료(종료)시 호출되는 핸들러 동록
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
private void buttonStart_Click(object sender, RoutedEventArgs e)
{
// 스레드가 Busy(즉, run)가 아니라면
if (bw.IsBusy != true)
{
// 스레드 작동!! 아래 함수 호출 시 위에서 bw.DoWork += new DoWorkEventHandler(bw_DoWork); 에 등록한 핸들러가
// 호출 됩니다.
bw.RunWorkerAsync();
}
}
private void buttonCancel_Click(object sender, RoutedEventArgs e)
{
// 해당 스레드가 스레드 취소를 지원한다면
if (bw.WorkerSupportsCancellation == true)
{
//스레드 취소(종료), 이 메소드를 호출하면 CancellationPending 속성이 true로 set됩니다.
bw.CancelAsync();
}
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; (i <= 10); i++)
{
//CancellationPending 속성이 true로 set되었다면(위에서 CancelAsync 메소드 호출 시 true로 set된다고 하였죠?
if ((worker.CancellationPending == true))
{
//루프를 break한다.(즉 스레드 run 핸들러를 벗어나겠죠)
e.Cancel = true;
break;
}
else
{
// 이곳에는 스레드에서 처리할 연산을 넣으시면 됩니다.
System.Threading.Thread.Sleep(500);
// 스레드 진행상태 보고 - 이 메소드를 호출 시 위에서
// bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged); 등록한 핸들러가 호출 됩니다.
worker.ReportProgress((i * 10));
}
}
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//바로 위에서 worker.ReportProgress((i * 10));호출 시
// bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged); 등록한 핸들러가 호출 된다고
// 하였는데요.. 이 부분에서는 기존 Thread에서 처럼 Dispatcher를 이용하지 않아도 됩니다.
// 즉 아래처럼!!사용이 가능합니다.
this.tbProgress.Text = (e.ProgressPercentage.ToString() + "%");
// 기존의 Thread클래스에서 아래와 같이 UI 엘리먼트를 갱신하려면
// Dispatcher.BeginInvoke(delegate()
// {
// this.tbProgress.Text = (e.ProgressPercentage.ToString() + "%");
// )};
//처럼 처리해야 할 것입니다. 그러나 바로 UI 엘리먼트를 업데이트 하고 있죠??
}
//스레드의 run함수가 종료될 경우 해당 핸들러가 호출됩니다.
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//스레드가 종료한 이유(사용자 취소, 완료, 에러)에 맞쳐 처리하면 됩니다.
if ((e.Cancelled == true))
{
this.tbProgress.Text = "Canceled!";
}
else if (!(e.Error == null))
{
this.tbProgress.Text = ("Error: " + e.Error.Message);
}
else
{
this.tbProgress.Text = "Done!";
}
}
}
}
소스를 통해서 좀더 자세히 알아보았는데요..사실 Thread클래스 이용시 취소에 관한 부분도 별도로 처리해야 하기 때문에
BackgroundWorker 클래스 사용이 편할 것 같네요..
그런데 주의할 점이 BackgroundWorker클래스가 제공하는 대로
BackgroundWroker클래스의 ReportProgress()메소드 호출 시 호출되는 핸들러에서 UI 엘리먼트를 갱신하는 것이 아닌 다른 부분에서
갱신시에는 Disptacher.BeginInvoke로 갱신해야 한다는 것 잊지마세요!!
유추해보면, 정확히 내부적으로 어찌 돌아가는지는 알 수 없지만, BackgroundWroker클래스에서 제공하는 방식으로 처리할 경우 BeginInvoke방식으로 처리한다고 짐작할 수 있겠죠?^^
즐은광~
[출처] 실버라이트 네이버 카페