본문 바로가기

Silverlight

Thread 클래스 vs BackgroundWorker 클래스

안녕하세요. 클라인스입니다.

그동안 이것저것 일들로 포스팅을 하지 못하였네요..ㅎ^^

금일 알아볼 내용은 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방식으로 처리한다고 짐작할 수 있겠죠?^^

즐은광~