4장. WPF 멀티쓰레드 프로그래밍

4장. WPF 멀티쓰레드 프로그래밍

참조

목적

  • WPF에서 어떻게 멀티쓰레드 프로그래밍을 하는지 알아봅니다.

멀티 쓰레드란?

  • 멀티쓰레드란 여러 개의 쓰레드가 동시에 특정 코드블럭을 실행하는 것이다.
  • 멀티쓰레드는 모든 부분에서 사용가능 하지만 채팅 프로그램처럼 내가 글을 쓰는 동안에 상대방이 글을 보내면 빠르게 반응해서 UI 화면에 그려야 하는 경우등에 주로 사용된다.
  • 모든 WPF 프로그램은 최소한의 렌더링을 위한 백그라운드 쓰레드와 UI 쓰레드(UI 인터페이스 관리) 두개의 쓰레드로 기동된다.
  • UI 쓰레드는 사용자 입력을 받고 화면을 그리고, 코드를 실행하고, 이벤트등을 처리한다.
  • WPF는 기본적으로 STA(Single Thread Apartment) 모델을 지원하는데 하나의 쓰레드는 전체 응용프로그램에서 실행되고 모든 WPF 객체를 소유하고 있고 TextBox 같은 WPF UIElements 요소들은 쓰레드 선호도라는 것이 있어 다른 쓰레드와 상호작용 할 수 없다. (UI 컨트롤은 다른 쓰레드에서 업데이트 할 수가 없다.)

BackgroundWorker란?

  • BackgroundWorker 클래스는 별도의 쓰레드에게 어떤 일을 시키기 위해 사용하는 클래스이다.
  • 흔히 백그라운드 쓰레드 혹은 워커 쓰레드라 불리우는 별도의 쓰레드에서는 UI Thread와 별도로 어떤 작업을 수행하는데 사용된다.
  • 백그라운드 워커(Background Worker)는 System.ComponentModel 아래의 클래스로 코드를 별도의 쓰레드에서 동시에 비동기적으로 실행하게 해 주는데 응용프로그램의 기본 쓰레드와 자동으로 동기화해준다. 호출 쓰레드는 정상적으로 실행이 되고 Background Worker는 백그라운드에서 비동기적으로 실행된다.
  • BackgroundWorker로부터 생성된 객체는 DoWork 이벤트 핸들러를 통해 실제 작업할 내용을 지정하고, ProgressChanged 이벤트를 통해 진척사항을 전달하며, RunWorkerCompleted 이벤트를 통해 완료 후 실행될 작업을 지정한다.
  • DoWork 이벤트 핸들러는 Worker Thread에서 돌고, ProgressChanged 와 RunWorkerCompleted 이벤트 핸들러는 UI Thread에서 돈다.
  • BackgroundWorker 클래스 객체는 Thread 클래스와 같이 쓰레드를 직접 생성하는 것이 아니라, Thread Pool로부터 가져온 쓰레드를 사용한다.

BackgroundWorker 사용 예제

//Thread 객체 생성
BackgroundWorker worker = new BackgroundWorker();

//이벤트 핸들러 지정
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worekr.ProgressChanged += new ProgressChangedEventHandler(worker_PorgressChanged);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);

//실행
worker.RunWorkerAsync();

실습

  • 숫자를 입력하면 백그라운드 워커를 통해 ProgressBar에 진행 사항을 표시하고, 리스트박스에 짝수들을 출력, 그리고 합을 구해 출력하는 예제이다.

MainWindow.xaml

<Window x:Class="WPF04_Test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF04_Test"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Label x:Name="label" 
               Content="숫자를 입력하세요 :"
               HorizontalAlignment="Left" 
               Margin="116,84,0,0" 
               VerticalAlignment="Top"/>
        <TextBox x:Name="txtNumber" 
                 HorizontalAlignment="Left"
                 Height="23" Margin="234,87,0,0"
                 TextWrapping="Wrap" 
                 Text="" 
                 VerticalAlignment="Top" Width="120"/>
        <Button x:Name="btnStart"
                Content="시작" 
                HorizontalAlignment="Left" 
                Margin="359,87,0,0" 
                VerticalAlignment="Top"
                Width="75"
                Click="btnStart_Click"/>
        <Button x:Name="btnCancel"
                Content="중지" 
                HorizontalAlignment="Left" 
                Margin="439,87,0,0" 
                VerticalAlignment="Top"
                Width="75"
                Click="btnCancel_Click"/>
        <ProgressBar x:Name="progressBar"
                    HorizontalAlignment="Left"
                     Height="32" Margin="116,125,0,0" 
                     VerticalAlignment="Top"
                     Width="398"/>
        <ListBox x:Name="listBox"
                 HorizontalAlignment="Left" 
                 Height="230" Margin="116,162,0,0"
                 VerticalAlignment="Top" Width="398"/>
        <Label x:Name="label1"
               Content="합계는?" 
               HorizontalAlignment="Left" 
               Margin="531,162,0,0" 
               VerticalAlignment="Top" 
               RenderTransformOrigin="0.308,-0.615"/>
        <TextBlock x:Name="txtSum" 
                   HorizontalAlignment="Left" 
                   Margin="588,167,0,0" 
                   TextWrapping="Wrap" 
                   Text="0" 
                   VerticalAlignment="Top"/>

    </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Threading;

namespace WPF04_Test
{
    /// <summary>
    /// MainWindow.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class MainWindow : Window
    {
        //백그라운드 워커 선언
        private BackgroundWorker worker;

        public MainWindow()
        {
            InitializeComponent();
            InitWorkerThread();
        }

        /// <summary>
        /// BackgroundWorker 객체 선언
        /// </summary>
        private void InitWorkerThread()
        {
            worker = new BackgroundWorker();
            worker.WorkerReportsProgress = true; //작업의 진행률이 바뀔때 ProgressChanged 이벤트 발생여부 체크
            worker.WorkerSupportsCancellation = true; //작업 취소 가능 여부 true 로 설정
            worker.DoWork += new DoWorkEventHandler(worker_DoWork); //해야할 작업을 실행할 메서드 정의
            worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged); //UI쪽에 진행사항을 보여줌, WorkerReportsProgress 속성값이 true 일때만 이벤트 발생
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);//작업이 완료되었을 때 실행할 콜백메서드 정의
        }


        /// <summary>
        /// 백그라운드 워커가 실행하는 작업
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            int num = 0;
            int sum = 0;
            int pct = 0;

            Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate { num = int.Parse(txtNumber.Text); }));

            for (int idx = 1; idx <= num; idx++)
            {
                //Check Status on each step
                if (worker.CancellationPending == true)
                {
                    e.Cancel = true;
                    return; //about work, if it's cancelled;
                }

                if (idx % 2 == 0) //2로 나눈 나머지가 0이라면 짝수라는 뜻
                {
                    sum += idx;

                    pct = idx++ * 100 / num;
                    worker.ReportProgress(pct);
                    Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate { listBox.Items.Add(sum); }));

                    Thread.Sleep(100);
                }
            }

            Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate { txtSum.Text = sum.ToString(); }));
        }

        /// <summary>
        /// 작업의 진행률이 바뀔때 발생, ProgressBar에 변경사항을 출력
        /// 대체로 현재의 진행상태를 보여주는 코드 여기에 작성
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar.Value = e.ProgressPercentage; //프로그레스바 진행상황 표시
        }

        private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //에러가 있는지 체크
            if(e.Error != null)
            {
                MessageBox.Show(e.Error.Message, "Error");
                return;
            }
        }

        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            listBox.Items.Clear();
            txtSum.Text = "0";

            // 비동기(Async)로 실행
            worker.RunWorkerAsync();
        }

        private void btnCancel_Click(object sender, RoutedEventArgs e)
        {
            //비동기(Async)로 중지
            worker.CancelAsync();
        }
    }
}

실행결과

1

  • BackgroundWorker 쓰레드를 사용할 때는 UI 쓰레드랑 동시에 접근할 수 없으므로, 그때 Dispatcher.Invoke 를 활용하여 사용하면 됩니다.
728x90

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

6장. WPF Data Trigger 란?  (0) 2021.05.08
5장. WPF 트리거란?  (0) 2021.05.06
3장. WPF C# 코드기반 HelloWrold  (0) 2021.05.06
2장. WPF 데이터바인딩 심플예제  (0) 2021.05.06
1장. WPF HelloWorld  (0) 2021.05.06

이 글을 공유하기

댓글

Designed by JB FACTORY