4장. WPF 멀티쓰레드 프로그래밍
- C#/WPF
- 2021. 5. 6. 18:55
4장. WPF 멀티쓰레드 프로그래밍
참조
- https://www.youtube.com/watch?v=KfY6DqWtcqs&list=PLxU-iZCqT52Cmj47aKB1T-SxI33YL7rYS&index=5
- https://www.csharpstudy.com/WinForms/WinForms-backgroundworker.aspx
목적
- 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();
}
}
}
실행결과
- 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 |
이 글을 공유하기