(WPF교육, WPF동영상, WPF학원)WPF 멀티쓰레드 프로그래밍, Dispatcher를 이용한 WPF 멀티쓰레드 프로그래밍
http://ojc.asia/bbs/board.php?bo_table=WPF&wr_id=155
ojc.asia
https://www.youtube.com/watch?v=KfY6DqWtcqs&list=PLxU-iZCqT52Cmj47aKB1T-SxI33YL7rYS&index=5&t=17s

https://www.youtube.com/watch?v=Wl2scRXoGPE&list=PLxU-iZCqT52Cmj47aKB1T-SxI33YL7rYS&index=1

1.5 WPF 멀티쓰레드 프로그래밍,
- 멀티쓰레드란 여러 개의 쓰레드가 동시에 특정 코드블럭을 실행하는 것이다.
- 멀티쓰레드는 모든 부분에서 사용가능 하지만 채팅 프로그램처럼 내가 글을 쓰는 동안에 상대방이 글을 보내면 빠르게 반응해서 UI 화면에 그려야 하는 경우등에 주로 사용된다.
- 모든 WPF 프로그램은 최소한의 렌더링을 위한 백그라운드 쓰레드와 UI 스레드(UI 인터페이스 관리) 두개의 쓰레드로 기동된다. UI 스레드는 사용자 입력을 받고 화면을 그리고, 코드를 실행하고, 이벤트 등을 처리한다.
- WPF는 기본적으로 STA(Single Thread Apartment) 모델을 지원하는데 하나의 쓰레드는 전체 응용프로그램에서 실행되고 모든 WPF 객체를 소유하고 있고 TextBox 같은 WPF UIElements 요소들은 “쓰레드 선호도”라는 것이 있어 다른 쓰레드와 상호작용 할 수 없다. (UI 컨트롤은 다른 쓰레드에서 업데이트 할 수가 없다.)
- 즉 화면을 그리는 쓰레드는 컨트롤들을 소유하고 다른 쓰레드에서는 직접 접근할 수 없도록 되어 있다. 이를 쓰레드 선호도라 한다.
- WPF에서 멀티 쓰레드 처리를 위해서 Dispatcher를 이용할 수도 있고 Background Worker를 사용할 수도 있다.
1.5.1 Dispatcher를 이용한 WPF 멀티쓰레드 프로그래밍
- 모든 비주얼 객체(TextBox, Button등)는 DispatcherObject 클래스를 상속받고 있으며 DispatcherObject가 UI 쓰레드의 Dispatcher 를 얻을 수 있게 해준다. 결국 이 Dispatcher 때문에 다른 쓰레드에서 UI 컨트롤을 변경 할 수 없는 것이다.
- TextBox 같은 컨트롤을 다른 쓰레드에서 수정하려면 Dispatcher를 통해서 해야 된다는 것이다.
- Dispatcher는 System.Windows.Threading.Dispatcher 클래스의 인스턴스로 응용 프로그램 쓰레드를 소유(쓰레드에 속한 모든 개체를 소유)하고 작업 항목의 대기열을 관리하는데 각각의 우선 순위를 사용하여 FIFO (First In First Out) 방식으로 UI 작업을 실행한다. 새 스레드를 만들지 않는다. 즉 멀티 스레드가 아니다.
- DispatcherObject 클래스의 멤버로는 다음과 같은 것이 있다.
- https://www.youtube.com/watch?v=Wl2scRXoGPE&list=PLxU-iZCqT52Cmj47aKB1T-SxI33YL7rYS&index=1

[속성]
Dispatcher : Dispatcher를 가지고 온다.
[메소드]
CheckAccess() : 코드가 객체를 사용할 수있는 스레드이면 true를 반환.
VerifyAccess() : 코드가 객체를 사용하기 위해 올바른 스레드인지 판단. 가능하다면 아무 작업도 수행하지 않고 그렇지 않으면 "InvalidOperationException"을 발생시킴.
- WPF 응용프로그램에서 수시로 자주 호출한다.
GetType () : 현재 인스턴스의 유형을 가져온다.
- 하나의 쓰레드에서 UIElement를 만들면 Dispatcher도 만들어지는데 새로운 TextBox를 만들면 거기에 따라 Dispatcher 객체도 만들어 진다.
- 대부분의 UI 객체(TextBox등)는 DisplatcherObject에서 파생되는데 이것은 Dispatcher와 연결된 객체라고 보면 된다.
- 만약 잘못된 쓰레드가 UI 객체에 접근하려고 하면 DispatcherObject의 VerifyAccess() 메소드가 “InvalidOperationException”을 던지도록 되어 있다. (WPF는 쓰레드의 잘못된 접근을 막기 위해 VerifyAccess 메소드를 자주 호출한다.)
- 아래 코드를 보자. (Invalid Operation Exception이 발생한다.)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Thread thread = new Thread(Update);
thread.Start();
}
private void Update()
{
Thread.Sleep(TimeSpan.FromSeconds(5));
txtName.Text = "홍길동";
}
}
- 위 코드를 Dispatcher를 사용하면 아래와 같이 재작성 할 수 있다.
(Dispatcher의 BeginInvoke를 사용하면 해당 메소드의 실행이 마무리 될 때까지 블록킹 된다.)
private void Update()
{
// Simulate some work taking place
Thread.Sleep(TimeSpan.FromSeconds(5));
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
(ThreadStart)delegate()
{
txtName.Text = "홍길동";
}
);
//this.Dispatcher.BeginInvoke(new Action(() =>
// {
// TxtName.Text = "Hello Geeks !";
// }));
}
Dispatcher.BeginInvoke() 메서드는 두 개의 파라미터가 있는데 첫 번째는 작업의 우선 순위를 나타내고 두 번째는 매개변수는 실행할 코드가 있는 메소드를 가리키는 대리자(Delegate) 이다.
1.5.2 Background Worker를 이용한 WPF 멀티쓰레드 프로그래밍
- Windows 응용 프로그램 멀티 쓰레딩에서 가장 어려운 개념은 다른 스레드에서 UI를 변경할 수 없다는 것이다. 대신 UI스레드에서 메소드를 호출해야 원하는 변경이 이루어 진다.
- 백그라운드 워커(Background Worker)는 System.ComponentModel 아래의 클래스로 코드를 별도의 쓰레드에서 동시에 비동기적으로 실행하게 해 주는데 응용프로그램의 기본 쓰레드와 자동으로 동기화 해준다. 호출 쓰레드는 정상적으로 실행이 되고 Background Worker는 백그라운드에서 비동기적으로 실행된다.
- 백그라운드에서 작업을 실행하고 UI 실행등을 연기하는데 사용되는데 사용자는 UI가 계속 반응하기를 원하면서 데이터를 다운로드 한다든지, 오래 걸리는 작업이 있어 진행사항을 표시해야 되는 경우, 데이터베이스 트랜잭션 처리 등에 유용하다.
- Background Worker에서 일어나는 작업에 대해 변경이 생길 때 호출되는 ProcessedChanged 이벤트, 작업이 완료되었을 때 무언가를 할 수 있도록 지원하는 RunWorkerCompledted 이벤트가 발생한다.
- DoWork 이벤트에서 백그라운드 쓰레드가 할일을 기술하는데 DoWork 이벤트 처리 메소드 내용은 다른 백그라운드, 다른 쓰레드에서 처리되므로 UI쪽을 접근할 수 없는데 ReportProgress() 메소드를 호출하면 ProcessChanged 이벤트가 발생하여 UI를 업데이트하는 것이 가능하다.
- ProgressChanged 및 RunWorkerCompleted 이벤트는 BackgroundWorker가 만들어지는 것과 동일한 스레드에서 실행된다.
- BackgroundWorker는 일반적으로 기본/UI 쓰레드이므로 UI를 업데이트 할 수 있다. 따라서 실행중인 백그라운드 작업과 UI간에 수행 할 수있는 유일한 통신방법은 ReportProgress() 메서드를 사용하는 것이다.
- DoWork 이벤트 처리 메소드 내부에서 파라미터가 필요하면 백그라운드 워커를 호출하는 RunWorkerAsync() 메소드의 인수로 넣어주면 된다.
int count = (int)e.Argument;
- DoWork 이벤트 처리 메소드 내부에서 e.Result 등으로 어떤 결과값을 넣어두면 RunWorkerCompledted 이벤트 처리 메소드에서 e.Result 형태로 꺼내볼 수 있다.
1.5.3 Background Worker를 이용한 WPF 멀티쓰레드 프로그래밍 실습
[간단히 Backgroud Worker를 사용한 예제를 작성해 보자.]
- 숫자를 입력하면 백그라운드 워커를 통해 ProgressBar에 진행 사항을 표시하고, 리스트 박스에 짝수들을 출력, 그리고 합을 구해 출력하는 예제이다.
- 1. BackgroundWorkerTest 하는 이름으로 WPF 프로젝트 생성

- MainWindow.xaml 파일을 더블클릭 후 디자인 화면에서 아래와 같이 디자인 하자.
- Label (Content 속성 : “숫자를 입력하세요”)
- TextBox (Name 속성 : txtNumber, Text 속성 : “”)
- Button(Name 속성 : btnStart, Text 속성 : “실행”)
- Button(Name 속성 : btnCancel, Text 속성 : “중단”)
- ProgressBar(Name 속성 : progressBar)
- ListBox(Name 속성 : lstNumber)
- Label(Content 속성 : “합계는”)
- TextBlock(Name 속성 : tblkSum, Text 속성 “”)


- MainWindow.xaml
<Window x:Class="BackgroundWorkerTest.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:BackgroundWorkerTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="359.844">
<Grid>
<Label Content="숫자를 입력하세요." HorizontalAlignment="Left" Margin="44,25,0,0" VerticalAlignment="Top" Width="121"/>
<TextBox x:Name="txtNumber" HorizontalAlignment="Left" Height="23" Margin="170,29,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="54"/>
<Button x:Name="btnStart" Content="실행" HorizontalAlignment="Left" Margin="235,29,0,0" VerticalAlignment="Top" Width="44" Height="23" Click="btnStart_Click"/>
<Button x:Name="brnCancel" Content="중단" HorizontalAlignment="Left" Margin="284,29,0,0" VerticalAlignment="Top" Width="41" Height="23" Click="btnCancel_Click"/>
<ProgressBar x:Name="progressBar" HorizontalAlignment="Left" Height="18" Margin="57,80,0,0" VerticalAlignment="Top" Width="268"/>
<ListBox x:Name="lstNumber" HorizontalAlignment="Left" Height="174" Margin="57,125,0,0" VerticalAlignment="Top" Width="100"/>
<Label Content="합계는 ?" HorizontalAlignment="Left" Margin="194,125,0,0" VerticalAlignment="Top" Width="108"/>
<TextBlock x:Name="tblkSum" HorizontalAlignment="Left" Margin="202,151,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="77" Height="20" Foreground="#FFFAF8F8">
<TextBlock.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="#FFB1A5A5" Offset="1"/>
</LinearGradientBrush>
</TextBlock.Background>
<TextBlock.OpacityMask>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush>
</TextBlock.OpacityMask>
</TextBlock>
</Grid>
</Window>
- MainWindow.xaml.cs
using System;
using System.ComponentModel;
using System.Windows;
using System.Threading;
using System.Windows.Threading;
namespace BackgroundWorkerTest
{
public partial class MainWindow : Window
{
//백그라운드 워커 선언
private BackgroundWorker myThread;
//짝수의 합을 저장할 인스턴스 변수
int sum = 0;
public MainWindow()
{
InitializeComponent();
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
//백그라운드 워커 초기화
//작업의 진행율이 바뀔때 ProgressChanged 이벤트 발생여부
//작업취소 가능여부 true로 설정
myThread = new BackgroundWorker()
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
//백그라운드에서 실행될 콜백 이벤트 생성
//For the performing operation in the background.
//해야할 작업을 실행할 메소드 정의
myThread.DoWork += myThread_DoWork;
//UI쪽에 진행사항을 보여주기 위해
//WorkerReportsProgress 속성값이 true 일때만 이벤트 발생
myThread.ProgressChanged += myThread_ProgressChanged;
//작업이 완료되었을 때 실행할 콜백메소드 정의
myThread.RunWorkerCompleted += myThread_RunWorkerCompleted;
MessageBox.Show("Worker 초기화");
}
//백그라운드 워커가 실행하는 작업
//DoWork 이벤트 처리 메소드에서 lstNumber.Items.Add(i) 와 같은 코드를
//직접 실행시키면 "InvalidOperationException" 오류발생
private void myThread_DoWork(object sender, DoWorkEventArgs e)
{
int count = (int)e.Argument;
for (int i = 1; i <= count ; i++)
{
if (myThread.CancellationPending)
{
e.Cancel = true;
return;
}
else
{
//메인 UI쓰레드 UI를 변경하기 위해서는
//idle Time을 둬야한다.
Thread.Sleep(100);
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
(ThreadStart)delegate ()
{
if (i % 2 == 0)
{
sum += i;
e.Result = sum;
lstNumber.Items.Add(i);
}
}
);
myThread.ReportProgress(i);
}
}
}
//작업의 진행률이 바뀔때 발생, ProgressBar에 변경사항을 출력
//대체로 현재의 진행상태를 보여주는 코드 여기에 작성.
private void myThread_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
}
//작업완료
private void myThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled) MessageBox.Show("작업 취소...");
else if (e.Error != null) MessageBox.Show("에러발생..." + e.Error);
else
{
tblkSum.Text = ((int)e.Result).ToString();
MessageBox.Show("작업 완료!!");
}
}
private void btnStart_Click(object sender, RoutedEventArgs e)
{
int num;
if (!int.TryParse(txtNumber.Text, out num))
{
MessageBox.Show("숫자를 입력하세요.!");
return;
}
progressBar.Maximum = num;
lstNumber.Items.Clear();
myThread.RunWorkerAsync(num);
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
myThread.CancelAsync();
}
}
}
#WPF멀티쓰레드, #WPF교육, #WPF학원, #WPF프로그래밍, #WPFDispatcher, #WPF동영상, #WPF멀티쓰레드프로그래밍
WPF멀티쓰레드, WPF교육, WPF학원, WPF프로그래밍, WPFDispatcher, WPF동영상, WPF멀티쓰레드프로그래밍