티스토리 뷰

C#

C# - BackgroundWorker에 관하여

takim 2021. 6. 28. 14:12

들어가는 말

 

 

제가 BackgroundWorker를 사용하게 된 배경으로 C#을 처음 만났을 때 입니다.

 

Java-Spring으로 API 만드는 것만 하다가 로컬 환경에서 비주얼 프로그래밍을 해야하는데

Java와 유사하다는 이야기를 듣고 C#으로 하면 되겠다는 생각을 하게 되었지요.

 

그리고 비주얼 프로그래밍 특성상 Main UI Thread 하나로 작동하다 보니 비동기 작업을 위해서는

Background 작업을 할 수 있는 것이 필요 했습니다.

 

지금 되돌아 보면 몇가지 아쉬운 점이 있는데요.

 

 

1. C# 언어의 패러다임과 비주얼 프로그래밍에 관해 알아 보았어야 한다.

 

C#에는 LINQ가 있습니다. 

SQL처럼 List에서 where 조건으로 찾는 방식이 가능하구요 Sorting 작업도 정의 되어 있습니다.

 

class IntroToLINQ
{
    static void Main()
    {
        // The Three Parts of a LINQ Query:
        // 1. Data source.
        int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };

        // 2. Query creation.
        // numQuery is an IEnumerable<int>
        var numQuery =
            from num in numbers
            where (num % 2) == 0
            select num;

        // 3. Query execution.
        foreach (int num in numQuery)
        {
            Console.Write("{0,1} ", num);
        }
    }
}

[출처] MSDN 예제  

 

 

그리고 discard가 java에도 있는지 모르겠지만

C#에서는 이런것이 가능하더라구요

private void test(){
	int ex = 10;
    _ = GetSomething(ex);
}

물론 사용의의가 오버로딩이라든지 그런 부분이 필요없을 때

유용하게 쓸 수 있다고 하였지만

현재까지 제가 사용할때는 어떤 함수의 사용하여 로직을 흘러가게 두고 결과가 필요없을 때 사용을 하였습니다.

( 물론 이것을 위해 쓰라고 만든게 아니겠지만서도요.. )

 

 

2. BackgroundWorker가 꼭 필요한지, 다른 것이 없는지에 대해 면밀히 알아봤어야 한다.

 

제가 백그라운드 작업을 찾으면서 BackgroundWorker를 의심없이 바로 사용하게 된 이유는

방식 때문이었습니다.

 

학교 졸업작품으로 안드로이드 어플리케이션을 하나 만들어야 했었는데요. 

 

여기서 서버의 데이터를 받아오기 위해 Http 통신을 해야하는데

어플리케이션에서도 윈도우 비주얼 프로그래밍과 비슷한 환경이지만 작업의 Timeout은 별도로 없습니다.

 

여기서는 윈도우 환경에 대해 이야기 할 것이라 간략히만 이야기하면

 

안드로이드 API 레벨이 정확히 기억은 나지 않는데 23(?) 27(?) 이었거나 했을 겁니다.

아이스크림 샌드위치가 제 뇌리속에 매여 있는거 보니 그것일 수도 있구요.

여튼 어떤 해당 버전 이후에는 Http 통신을 주구장창 기다려 주지 않게 되었다는 것을 확인했습니다.

 

안드로이드 ANR ( Application not responding )이 발생해서 앱이 계속 죽는 겁니다.

 

알아보니 결과 받는데 오래 걸리는 작업은 ( 대략 20초 정도로 알고 있습니다. ) Background로 작업을 넘겨줘야 한다는 것을 발견하고 해당 예제를 찾아 적용했습니다.

 

거기서 확인 했던것이 Dowork, DoProgress, DoComplete 로 뒤에 두가지는 가물가물 하지만 Dowork는 DoInwork로 예제를 활용 했던 것이 기억 속에 있어서 C# Background로 구글링 했을 때 많이 나오던 것이 BackgroundWorker 였습니다.

 

그리고 방식도 비슷 했습니다. Dowork , Doprogress, Docomplete가 있었습니다.

바로 사용을 했는데 여기서 작업을 한다 (Task)에 조금 더 착안해서 검색을 해보았다면 좀 더 많은 고민과 많은 자료를 찾을 수 있었을 것이라 생각합니다. 아쉽지만 그 뒤에도 사용하다가 이제는 알게 되었습니다.

 

 

 

 

사용법

 

MSDN에 피보나치 수열에 관한 예제가 있어 해당 부분 사용 했습니다.

 

 

 

1부터 위에 입력한 숫자까지의 피보나치 수열의 합을 구해주는 것입니다.

 

중간에 Cancel을 눌러 종료할 수 있고 중간에 바에서 얼마나 진행중인지 확인 할 수도 있습니다.

 

 

중간에 취소하면 해당 이미지와 같이 나오고

 

 

처리가 되면 해당 합의 값이 나옵니다.

 

 

코드는요.. 

MSDN에서 가져왔습니다.

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace BackgroundWorkerTest
{
    public partial class Form1 : Form
    {
        private BackgroundWorker test_bw = new BackgroundWorker();
        private int numberToCompute = 0;
        private int highestPercentageReached = 0;

        public Form1()
        {
            InitializeComponent();

            //스레드 작업 도중 취소 가능 여부
            test_bw.WorkerSupportsCancellation = true;
            //스레드 작업 진행상황 가능 여부
            test_bw.WorkerReportsProgress = true;

            InitializeBackgroundWorker();

        }
        private void InitializeBackgroundWorker()
        {
            //스레드가 run시에 호출되는 핸들러 등록
            test_bw.DoWork += new DoWorkEventHandler(test_bw_DoWork);
            // ReportProgress메소드 호출시 호출되는 핸들러 등록
            test_bw.ProgressChanged += new ProgressChangedEventHandler(test_bw_ProgressChanged);
            // 스레드 완료(종료)시 호출되는 핸들러 동록
            test_bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(test_bw_RunWorkerCompleted);
        }

        private void btn_Start_Click(object sender, EventArgs e)
        {
            // 결과 텍스트 지우기
            lb_result.Text = String.Empty;

            // 숫자 조정하는 것 비활성화
            this.numericUpDown1.Enabled = false;

            // 시작버튼 비활성화
            this.btn_Start.Enabled = false;

            // 취소버튼 비활성화
            this.btn_Stop.Enabled = true;

            // 숫자 조정하는 곳에서 값 가져오기
            numberToCompute = (int)numericUpDown1.Value;

            // 퍼센트 0으로 초기화
            highestPercentageReached = 0;

            // 백그라운드 작업 실행
            test_bw.RunWorkerAsync(numberToCompute);
        }

        private void btn_Stop_Click(object sender, EventArgs e)
        {
            // 백그라운드 작업 중지
            this.test_bw.CancelAsync();

            // 캔슬버튼 비활성화
            btn_Stop.Enabled = false;
        }

        private void test_bw_DoWork(object sender, DoWorkEventArgs e)
        {
            // 인자값 받아오기 위해...
            BackgroundWorker worker = sender as BackgroundWorker;

            // 결과값에 피보나치 결과 입력
            // DoworkEventArgs에 결과가 남겨져 있다.
            // 현재 결과는 test_bw_RunWorkerCompleted에서 확인 가능합니다.
            e.Result = ComputeFibonacci((int)e.Argument, worker, e);

        }
        private void test_bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // 에러 처리
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }
            else if (e.Cancelled)
            {
               // 만약 중간에 종료가 된 것이라면 Canceled로 표시
                lb_result.Text = "Canceled";
            }
            else
            {
                // 완료가 되었다면 결과값 입력
                lb_result.Text = e.Result.ToString();
            }

            // 숫자 조정 활성화
            this.numericUpDown1.Enabled = true;

            // 시작 버튼 활성화
            btn_Start.Enabled = true;

            // 종료 버튼 비활성화
            btn_Stop.Enabled = false;
        }


        private void test_bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.progressBar1.Value = e.ProgressPercentage;
        }

        

        long ComputeFibonacci(int n, BackgroundWorker worker, DoWorkEventArgs e)
        {
            // 파라미터는 0보다 크고 91보다 작아야 합니다.
            // 91보다 클 경우 long이 오버플로우가 됩니다.
            if ((n < 0) || (n > 91))
            {
                throw new ArgumentException(
                    "value must be >= 0 and <= 91", "n");
            }

            long result = 0;


            // 유저가 취소 했다면 DoWorkEventArgs에 Cancel되었다고 입력합니다.
            if (worker.CancellationPending)
            {
                e.Cancel = true;
            }
            else
            {
                if (n < 2)
                {
                    result = 1;
                }
                else
                {
                    result = ComputeFibonacci(n - 1, worker, e) +
                             ComputeFibonacci(n - 2, worker, e);
                }

                // 중간 처리 값을 지속적으로 보고 한다.
                int percentComplete =
                    (int)((float)n / (float)numberToCompute * 100);
                if (percentComplete > highestPercentageReached)
                {
                    highestPercentageReached = percentComplete;
                    worker.ReportProgress(percentComplete);
                }
            }

            return result;
        }

    }
}

 

 

 

numericUpDown 1개가 있습니다.

그리고 그 오른쪽에 Label이 하나 있고 name은 lb_result입니다.

 

중간에 progressbar가 있고,

 

왼쪽 버튼은 btn_start , 오른쪽 버튼은 btn_Stop입니다.

 

 

 

만약 백그라운드 작업을 하지 않게 되면 UI가 멈추게 됩니다.

그리고 작업관리자에서 해당 프로세스를 확인하면 '프로세스이름(응답없음)'이라고 표시 될 것입니다.

 

Winodws Form이라는 1개의 프로세스가 있는데 이것은 Main UI Thread에서 작업을 할 시 다른 UI 조작이 불가능 해지게 되고 그러다 보니 클릭 이벤트를 처리하지 못해 응답이 없다는 진단결과가 나오게 됩니다.

 

 

그런데 BackgroundWorker를 사용하는 것 보다 멀티 코어 프로그래밍이 필요한 순간이 있을 수도 있습니다.

 

 

마무리

 

아직까지 의문이 되는 부분이 있지만

BackgroundWorker는 Component 클랫스를 상속받았고 Component클래스는 MarshalByRefObject 클래스를 상속 받았습니다.

 

그런데 Task는 바로 Object에서 Task로 넘어옵니다.

 

그래서 BackgroundWorker가 .net 2.0에서 처음 나왔고 Task가 .net 4.0에서 처음 나온것을 미루어 볼 때

 

비동기 작업은 Task로 처리하는게 간결하게 사용 할 수 있지 않을까 합니다.

 

물론 둘다 동일한 것이 ThreadPool에서 Thread를 할당 받아 수행 한다는 점입니다.

 

이 부분은 매우 중요한데

 

CPU의 코어가 1개가 있다면 Multi Threading이 단순 할 것입니다.

시분할로 이루어져 작동 할 것이라 예상 가능하지만

 

멀티 코어라면 Thread가 제각각 다른 코어에 할당 되어 진짜 동시에 작업이 이루어 질 수 있을 것입니다.

 

멀티 스레딩이 아는 만큼 더 많은 고민을 하게 만드는게 아닌가 싶습니다.

학교 다닐때의 멀티스레딩과 현재의 멀티스레딩은 사뭇 느낌이 다릅니다.

 

그래서 병렬처리에 대해 이야기하는 Task에 대해 더 알아 보고 어떻게 사용하는게 옳은 지에 대해 고민해본 것을 정리 해보고자 합니다.

 

여유가 된다면 WinForms 와 WPF에 대해서도 정리를 해보아야 겠습니다.

 

그러면 자연스럽게 MVC 모델과 MVVM 모델에 대해서도 정리를 해야 겠지요.. 

 

양이 많아지는 느낌이네요.

 

MVC 모델과 MVVM 모델은 정말 구분이 안갔습니다. 왜 굳이 MVVM이라는 모델이 나온 것이지? 

굳이 만들 이유가 있나? 성능차이에 영향을 주는 것인가? 하는 문제에 대해서

 

WPF를 사용하면서 MVVM 모델을 사용 해 보았는데 그 이유에 어느 정도 이해가 가능하게 되었습니다.

 

앞으로는 아마 Task에 대한 정리 이후 비주얼 프로그래밍에 대한 정리를 해볼 생각입니다.

'C#' 카테고리의 다른 글

폴더관리 프로그램  (0) 2022.10.20
메모리 스트림 사용에 관해  (0) 2021.10.19
Task와 Thread와 BackgroundWorker의 관해서  (2) 2021.09.02
Task 관련 테스트  (0) 2021.07.20
C#의 역사  (0) 2021.06.20
댓글