Creative Commons License

Microsoft .NET

닷넷!시작하기
닷넷! Ver 2.0~
닷넷!스킬업
웹개발
윈폼개발
실용모듈개발
Tip & Tech
하루 한 문법

Microsoft .NET 개발자들을 위한 공간입니다. 기초강의에서 부터 고급 기술 정보 및 팁등을 다루도록 하겠습니다.

.

윈폼개발

닷넷을 기반으로 한 윈도우 응용프로그램 개발 지식을 다룹니다. 비지니스 응용프로그램을 위한 닷넷 윈폼 응용프로그램은 아주 강력합니다

[컴포넌트, 비동기작업] BackgroundWorker

작성자 : 박종명
최초 작성일 : 2008-06-26 (목요일)
최종 수정일 : 2008-06-26 (목요일)
조회 수 : 3797

이번 포스트에서는 비동기 작업을 지원하는 BackgroundWorker 컴포넌트에 대해 알아 보겠습니다.

 

참고로 구성요소로 번역되는 컴포넌트는 컨트롤과는 달리 UI 를 가지고 있지 않습니다.

아래처럼 Visual Studio 의 도구상자를 통해 폼에 이 컴포넌트를 올리면 폼에 특별한 UI 는 나타나지 않은 채

하위 컴포넌트 목록 창에만 표시됩니다.

 

.. 중요한 내용은 아니었구요..

 

비동기 작업이란 수행 시간이 오래 걸리는 작업을 백그라운드 상에서 별도로 처리한게 하는 방법입니다.

즉 메인 쓰레드와 분리된 쓰레드에서 작업을 수행하도록 하여 응용프로그램의 블럭을 방지하고 지연을

최소화하는 프로그래밍 방식입니다.

 

MSDN에서는 수행시간이 오래 걸리는 작업을 다음처럼 예시하고 있습니다.

?  이미지 다운로드

?  웹 서비스 호출

?  파일 다운로드 및 업로드(피어--피어 응용 프로그램에 대한 다운로드 및 업로드 포함)

?  복잡한 로컬 계산

?  데이터베이스 트랜잭션

?  로컬 디스크 액세스(메모리 액세스에 비해 상대적으로 속도가 느림)

 

그리고 MSDN의 설명 한 마디를 보시죠..

"BackgroundWorker 구성 요소를 사용하면 응용 프로그램의 주 UI 스레드와 다른 스레드에서 시간이 많이 소모되는

작업을 백그라운드"에서 비동기적으로 실행할 수 있습니다"

 

BackgroundWorker 컴포넌트는 닷넷 2.0 부터 새로이 추가된 컴포넌트 입니다.

물론 이전 버전에서도 사용할 수 있었지만 닷넷 프레임워크에 포함된 형태는 아니었었습니다.

 

BackgroundWorker 를 사용하기 전에는 이러한 비동기 작업을 처리하기 위해서 수동으로 작업자 쓰레드(Worker Thread)

직접 생성해서 주 쓰레드(Main Thread) 와 분리하여 작업을 처리하는 코드를 직접 작성했어야 했으며,

작업 진행상황이나 작업 취소와 같은 일련의 작업을 개발자가 직접 해 주어야 했습니다.

쓰레드에 관해서는 다음의 사이트의 글들을 참고 하시기 바랍니다.

 

Process & Thread 개요

Mulit Thread Programming

ThreadState (Thread 상태)

공급자/소비자 문제로 알아보는 동기화 문제

[동기화] Monitor 클래스

[동기화] Interlocked 클래스 와 lock

[동기화] Mutex 클래스

[동기화] Event

 

이렇듯 멀티 쓰레드 프로그래밍을 작성할 때는 다양한 이슈가 있습니다.

쓰레드 자체에 대한 자세한 내용은 위의 링크 및 기타 다른 자료로 학습을 하시면 되겠습니다.

이번 글에서는 BackgroundWorker 컴포넌트의 사용방법에 대해서 다루겠습니다.

 

* BackgroundWorker  이벤트

이 컴포넌트를 사용하여 비동기 작업을 하고하 할 경우 알아야 이벤트에 대해서 알아 보겠습니다.

BackgroundWorker 컴포넌트에는 3개의 이벤트가 있습니다.

 

1) DoWork

    백그라운드 작업의 실제 실행을 처리하는 이벤트 입니다.

    단 이 이벤트는 자동으로 발생하지 않고 BackgroundWorker RunWorkerAsync 메서드가 호출될 때 발생하게 됩니다.

    즉 개발자가 비동기 작업을 위해 RunWorkerAsync 메서드를 호출하면 자동으로 DoWork 이벤트가 발생하게 됩니다.

    RunWorkerAsync -> DoWork

 

2) ProgressChanged

    백그라운드 작업의 진행률을 처리하기 위한 이벤트 입니다.

    이 이벤트 역시 개발자가 수종으로 발생 시켜줘야 하는데,

    BckgroundWorker ReportProgress 메서드를 명시적으로 호출하여 이벤트를 발생시킬 수 있습니다.

    또한 연관된 속성으로는 WorkerReportProgress 로써 이 속성이 참(true) 로 설정되어 있어야 됩니다.

    ReportProgress -> ProgressChanged

 

3) RunWorkerCompleted

    백그라운드 작업의 완료, 중도 취소, 예외가 생겼을 때 발생하는 이벤트 입니다.

    연관된 속성으로는 WorkerSupportsCancellation 로써 이 속성이 참(true) 로 설정되어 있어야 합니다.

    이 이벤트의 매개변수인 RunWorkerCompletedEventArgs 객체를 통해 작업이 중도에 취소되었는지, 예외가 발생했는지

    를 알 수 있습니다.

 

이상 3가지 이벤트를 사용하여 손 쉽게 백그라운드 작업을 구현하실 수 있게 됩니다.

   

 

* Demo

일단 다음과 같이 오래 걸리는 작업을 수행하는 메서드(DelayMethod)를 작성하도록 합니다.

현재 이 글에서는 이 작업이 무엇을 하는지가 중요한게 아니라 오래 걸리는 작업이 중요한 포인터이기 때문에

실제로 오래걸릴만한 작업이 아닌 의도적으로 중간에 Sleep 를 걸어 두도록 하겠습니다.

 

다음의 코드가 작업이 오래 걸리는 역할을 하는 메서드 정의입니다.

매개변수로 최대값을 받아서 순차적으로 더해 갑니다. 그리고 최종 더한 값을 반환하도록 합니다.

메서드의 지연처리를 위해 중간에 Thread.Sleep 으로 수행을 잠시 멈춥니다.

(실제로 이런 코드는 있을 수는 없겠죠.. 하지만 테스트를 위해 아래처럼 작성하도록 합니다)

 private Int64 DelayMethod(int maxNumber)
 {
      Int64 result = 0;
      for (int i = 1; i <= maxNumber; i++)
      {
          result += i;
          System.Threading.Thread.Sleep(100);

      }

      return result;
 }

 

 

1. 비동기로 처리 하지 않을 경우

위 메서드를 백그라운드에서 처리하지 않고 동기방식으로 처리하도록 해 보겠습니다.

폼에 버턴을 하나 두고 버턴을 클릭하면 위 메서드가 수행되고 끝나면 결과값을 Label 에 기록하는 간단한 샘플입니다.

버턴이 클릭 되었을 때 다음처럼 메서드를 호출합니다.

 

Int64 result =  DelayMethod(100);
this.lblResult.Text = result.ToString();

 

최대값을 100 을 주었고 중간중간에 100밀리 세컨드 만큼 수행을 멈추므로 이 작업은 시간이 조금 걸리는 작업이 됩니다.

직접 실행을 해 보면 버턴을 클릭하고 난 후 부터 작업이 완료될 때 까지 폼이 멈춰버리고 다른 어떠한 작업도 할 수 없게 됩니다.

또한 이 폼을 다시 보이게 하려면 다음처럼 하얀색 빈 바탕 화면처럼 나오게 됩니다.

 

= 원래 폼 모양 =

   

 

= 수행 도중 창을 내렸다가 다시 올렸을 경우의 모양 =

 

시간이 오래 걸리는 작업을 동기로 구현한다면 이러한 문제가 발생합니다.

UI 쓰레드에서 직접 메서드를 수행하기 때문에 다른 곳에서는 제어권을 받을 수 가 없게 되는 것입니다.

 

 

2. BackgroundWorker 를 이용해 비동기로 처리하기.

 

이제 위 메서드를 비 동기로 처리 해 보겠습니다.

우선 폼의 모양을 아래처럼 조금 다듬습니다.

 

최대값을 사용자로 부터 입력 받기 위한 NumericUpDown 컨트롤과 작업의 시작,취소 버턴

그리고 작업 진행률을 보기 위한 ProgressBar , 결과값 출력을 위한 Lable 컨트롤을 적절히 배치 하였습니다.

이 샘플은 msdn BackgroundWorker 샘플 중 피보나치 수열을 계산하는 샘플을 조금 변형한 것입니다.

 

 

비 동기 작업의 진행률과 취소를 지원하기 위해 두개의 속성을 설정합니다.

    (이 코드에서는 Form_Load 이벤트에서 작성합니다. 디자인 타임에 속성 창을 통해서 하셔도 됩니다)

   

 //최대값 저장을 위한 멤버 변수

 int maxNumber = 10; 

 

 //폼 로드 이벤트

 private void Form1_Load(object sender, EventArgs e)
 {
      //작업 진행률을 보고할 수 있도록 설정한다.
      this.backgroundWorker1.WorkerReportsProgress = true;
      //작업을 취소할 수 있도록 설정한다.
      this.backgroundWorker1.WorkerSupportsCancellation = true;
 }

 

다음으로 '작업 시작' 버턴을 클릭했을 때 다음 처럼 비동기 작업을 시작하도록 합니다.

 private void startAsyncButton_Click(object sender, EventArgs e)
 {

      //시작된 이후에 필요하지 않은 컨트롤은 사용 불가 처리 한다.

      this.numericUpDown1.Enabled = false;
      this.lblResult.Text = String.Empty;                                  
      this.startAsyncButton.Enabled = false;           
      this.cancelAsyncButton.Enabled = true;

 

      // 최대값 얻기
      int maxNumber = (int) numericUpDown1.Value;

 

      // 진행률 퍼센트 계산을 위한 변수 초기화
      highestPercentageReached = 0;

 

      //백그라운드 작업을 시작한다.이 메서드 호출은 DoWork 이벤트를 발생시킨다

      //이때 매개변수로 최대값을 넘겨 준다
      backgroundWorker1.RunWorkerAsync(maxNumber);
 }

 

 

이제 본격적으로 BackgroundWorker 컴포넌트의 이벤트를 작성합니다.

 

= DoWork 이벤트 =

 private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
 {           
      BackgroundWorker worker = sender as BackgroundWorker;
           
      //
실제로 백그라운드에서 돌아갈 메서드를 호출한다 

      //이때 앞서 RunWorkerAsync 메서드 호출 시 전달한 매개변수를 e.Argument 로 다시 넘긴다.

      //그리고 비동기 작업의 결과 값을 EventArgument Result 에 저장한다.
      e.Result = this.DelayMethod((int)e.Argument, worker, e);

 }

 

= ProgressChanged 이벤트 =

 //BackgroundWorker ReportProgress가 호출될때 발생한다. 비동기 작업 진행률을 보고 받는다
 private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
 {

      //ProgressBar 에 진행률을 표시한다
      this.progressBar1.Value = e.ProgressPercentage;
 }

 

= RunWorkerCompleted 이벤트 =

 //백그라운드 작업이 완료되거나 취소되거나 예외를 발생시켰을 때 발생합니다
 private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
 {            
      //
작업에 오류가 발생하면 메세지를 띄운다
       if (e.Error != null)
      {
          MessageBox.Show(e.Error.Message);
      }
      //
작업이 취소되었으면 취소되었다는 메세지를 보여준다
      else if (e.Cancelled)
      {                
          lblResult.Text = "
작업이 취소되었습니다";
      }
      //
작업이 완료되면 결과값을 보여준다
      else
      {
          //
결과값 출력
          lblResult.Text = e.Result.ToString();
      }
           
      //
기타 컨트롤 사용가능케 설정
      this.numericUpDown1.Enabled = true;            
      startAsyncButton.Enabled = true;           
      cancelAsyncButton.Enabled = false;
 }

 

취소 버턴 클릭 이벤트를 작성합니다

 private void cancelAsyncButton_Click(object sender, EventArgs e)
 {
      //
비동기 작업을 취소를 요청한다.

      //이 메서드 호출로 BackgroundWorker CancellationPending 속성이 true로 설정한다
      this.backgroundWorker1.CancelAsync();

      //취소 버턴 비활성화
      cancelAsyncButton.Enabled = false;
 }

 

비동기로 수행될 메서드

실제 시간이 오래 걸리는 작업을 하는 메서드 정의 입니다.

기존 비지니스 처리 이외에 BackgroundWorker 의 작업 취소 감지 및 진행률 보고 로직이 포함되어야 합니다

 private Int64 DelayMethod(int maxNumber, BackgroundWorker worker, DoWorkEventArgs e)
 {
      Int64 result = 0;


      for (int i = 1; i <= maxNumber; i++)
      {
          //
백그라운드 작업을 취소 했는지 검사한다.
         if (worker.CancellationPending)
         {
              e.Cancel = true;
         }
         else
         {
              result += i;

 

              //진행률을 % 로 계산한다.
              int percentComplete = (int) ((float)i / (float)maxNumber * 100);

              if (percentComplete > highestPercentageReached)
              {
                  highestPercentageReached = percentComplete;

 

                  //ProgressChanged 이벤트를 발생시킨다
                  //
작업의 완료률을 0~100 숫자로 보고한다
                  worker.ReportProgress(percentComplete);
               }

 

              //지연을 위해 잠시 멈춘다
              Thread.Sleep(100);
         }
    }       

      //수행을 마치면 결과 값을 반환한다
      return result;            
 }

 

이제 코드 작성이 완료 되었습니다.

첨부파일에 이 글에 사용된 샘플 프로젝트가 등록되어 있으니 다운받으셔서 직접 실행 시켜 보시기 바랍니다.

아래 그림은 프로젝트를 실행완료한 화면 입니다.

 

이상 닷넷 2.0 에 새로 추가된 BackgroundWorker 컴포넌트에 대해 간략히 살펴 보았습니다.

 

 

 

이름
비밀번호
홈페이지
LI <- 왼쪽의 문자를 오른쪽 박스에 똑같이 입력해 주세요