Study/C#

WPF multi-thread (dispatcher, background worker)

13.d_dk 2021. 4. 14. 21:56
728x90
반응형

멀티스레드(Multi-thread)란?

 컴퓨터에서 프로그램을 사용할 수 있다. 이러한 프로그램을 사용하면 컴퓨터 내부에서는 하나의 프로세스(Process)에 하나의 프로그램을 할당하여 다룬다. 프로그램에서 여러 기능을 동시에 사용할 수 있다. 게임을 예시로 들어보자. 컴퓨터 게임은 하나의 프로그램이다. 이 게임에서는 배경음악이 흘러나온다. 동시에 방향키의 입력을 받아서 게임 상의 캐릭터를 조종할 수 있다. 이와 같은 동작은 하나의 프로세스 내부에서 여러 스레드(thread)가 각각의 기능을 수행할 수 있기 때문이다. 예시로 설명한 게임에서 배경음악을 실행하는 스레드가 동작 중인 동시에 방향키의 입력을 받으면 캐릭터를 조종하는 스레드가 동작 중이라고 볼 수 있다. 이러한 여러 스레드를 사용하는 것을 멀티스레드(multi-thread)라고 한다.

게임을 예시로 하는 프로세스와 그 안의 여러 쓰레드들

 

C# WPF에서 멀티스레드

 C# WPF로 개발된 프로그램은 기본적으로 UI 인터페이스를 관리하는 UI 스레드와 비 UI 스레드가 있다. 기본적인 코드의 실행 및 사용자의 입력을 받고 이벤트를 처리하는 등의 동작은 UI 스레드에서 수행된다. 비 UI 스레드는 어떤 무거운 동작을 처리할 때 사용할 수 있다. 복잡하고 시간이 오래 걸리는 연산을 처리하여 결과를 화면에 출력해야 하는 경우를 생각해보자. 복잡하고 시간이 오래 걸리는 연산은 비 UI 스레드에서 처리하고 있고 사용자가 다른 동작을 할 수 있게 UI 스레드가 기본적인 동작을 수행한다. 시간이 지나 비 UI 스레드에서 연산을 완료하여 결과를 출력할 수 있게 되면 UI 스레드를 통해 화면에 결과를 출력할 수 있다.

 C# WPF는 기본적으로 STA(Single Thread Apartment) 모델을 지원한다. 이는 하나의 기본 스레드가 전체 응용 프로그램에서 실행되고 이 스레드에 모든 WPF 객체가 종속되어 있다. 다른 스레드에서 이러한 WPF 객체(UI control)를 업데이트하려고 하면 에러가 발생한다. 다시 설명하면 기본 스레드를 제외한 다른 스레드에서는 WPF 객체에 접근하여 다룰 수 없으며 이를 스레드 선호도라고 명명한다.

WPF에서 thread 관계 예시

 

 이러한 STA 모델을 지원하는 C# WPF에서 멀티 스레드를 다루는 방법은 2가지이다. 하나는 Dispatcher를 사용하는 것이고 다른 하나는 Background Worker를 사용하는 것이다.

 

Dispatcher를 사용하여 WPF에서 멀티스레드 다루기

 Dispatcher를 사용하는 멀티 스레드는 언제 사용할까? 이는 UI에 어떤 요소를 잠깐 다른 스레드에서 업데이트하거나 다룰 때 사용된다. 시간이 소요되는 연산 끝에 UI의 화면에 결과를 출력하는 동작이 예시가 될 수 있다. 이러한 동작이 어떻게 이루어질까? 모든 UI 컨트롤들(Label, TextBox, Button 등)은 DispatcherObject 클래스를 상속받았다. 이 DispatcherObject 클래스는 기본 UI 스레드가 Dispatcher를 얻을 수 있게 한다. 만약 이러한 UI 컨트롤에 접근하고자 한다면 Dispatcher를 통해 접근할 수 있다.

 Dispatcher는 System.Windows.threading.Dispatcher 클래스의 인스턴스이다. 이 Dispatcher는 UI 컨트롤의 작업 항목의 대기열을 관리한다. 이 대기열은 FIFO(First In First Out) 방식으로 UI 작업을 할 수 있게 한다. 정확하게 보자면 Dispatcher를 사용하는 것은 멀티 스레드는 아니다.

 C# WPF에서 UI 컨트롤과 관련하여 값의 변화를 할 때 자주 보는 에러는 "InvalidOperationException"이다. 이 에러는 잘못된 스레드가 UI 컨트롤을 접근할 때 발생한다. 이러한 에러가 발생한 경우 Dispatcher를 사용하여 해결할 수 있다.

 아래의 예시는 Dispatcher를 사용하여 다른 스레드에서 UI 컨트롤을 접근하는 예시이다. 일정 시간이 지난 후 Label의 텍스트가 업데이트되는 것을 확인할 수 있다.

 아래의 예시는 Dispatcher를 사용하여 다른 스레드에서 UI 컨트롤을 접근하는 예시이다. 일정 시간이 지난 후 Label의 텍스트가 업데이트되는 것을 확인할 수 있다. 이 예제에서 Thread와 Dispatcher를 사용하기 위해 System.Windows.threading.Dispatcher와 System.Thread 네임스페이스를 정의해야 한다. 주석으로 Error라고 작성된 코드를 실행하면 잘못된 스레드가 UI 컨트롤에 접근하여 "InvalidOperationException" 에러가 발생한다. 주석으로 되어 있지 않은 부분과 같은 Dispatcher를 사용하면 에러 없이 코드가 수행됨을 확인할 수 있다. xaml 및 xaml.cs 예제는 아래와 같다.  

  • MainWindow.xaml
1
2
3
4
5
6
7
8
9
<Window x:Class="WpfThreadDispatcher.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Label x:Name="txtLbl" Content="start text" FontSize="33" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Grid>
</Window>
 
 
cs

 

  • MainWindow.xaml.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Threading;
 
namespace WpfThreadDispatcher
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Thread thread = new Thread(Update);
            thread.Start();
        }
 
        private void Update()
        {
            Thread.Sleep(TimeSpan.FromSeconds(5));
 
            // Error! ... InvalidOperationException
            // txtLbl.Content = "Change txt";
 
            // Do This!
            this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                (ThreadStart)delegate()
                {
                    txtLbl.Content = "Change txt";
                });
        }
    }
}
 
 
cs

 

Background worker를 사용한 WPF에서 멀티스레드 다루기

 Background worker를 통한 멀티 스레드는 기본 프로그램 동작과 함께 동시에 처리되어야 하는 어떤 동작이 필요할 때 사용한다. 지속적으로 재생되는 배경음악과 같은 동작이 예시가 될 수 있다. Background Worker는 System.ComponentModel 아래의 클래스이다. 이 Background worker는 코드를 비동기적으로 별도의 스레드에서 실행할 수 있게 한다. 또 이는 기본 UI 스레드와 자동으로 동기화가 된다. 프로그램을 다루는 메인 스레드가 실행되는 도중 background에서 비동기적으로 실행되는 것이다.

 아래의 예시는 background worker를 사용하여 0에서 10000까지 200ms 간격으로 올라가는 중 원하는 숫자를 캡처하는 프로그램이다. UI 스레드가 버튼을 눌렀을 때 올라가는 숫자 중 하나를 캡처할 수 있게 한다. 동시에 background worker를 통한 다른 스레드는 0에서 10000까지 숫자를 계속해서 올린다. 여기서 UI 컨트롤에 접근하여 숫자가 바꾸어야 하므로 Dispatcher를 사용하였다.

 아래의 예시는 background worker를 사용하여 0에서 10000까지 200ms 간격으로 올라가는 중 원하는 숫자를 캡처하는 프로그램이다. UI 스레드가 버튼을 눌렀을 때 올라가는 숫자 중 하나를 캡처할 수 있게 한다. 동시에 background worker를 통한 다른 스레드는 0에서 10000까지 숫자를 계속해서 올린다. 여기서 UI 컨트롤에 접근하여 숫자가 바꾸어야 하므로 Dispatcher를 사용하였다. background worker를 사용하기 위해 System.ComponentModel 네임스페이스를 추가하였다. 또 Dispatcher를 사용하기 위해 System.Threading, System.Windows.Threading 네임스페이스를 추가하였다. xaml 및 xaml cs 예제는 아래와 같다.

  • MainWindow.xaml
1
2
3
4
5
6
7
8
9
10
11
12
13
<Window x:Class="WpfBackGround.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <StackPanel>
            <Button x:Name="btn" Content="capture number" FontSize="33" Click="btn_Click"/>
            <Label x:Name="txtLbl" Content="NULL" FontSize="33" HorizontalAlignment="Center" VerticalAlignment="Center"/>
            <Label x:Name="txtBackLbl" Content="number" FontSize="33" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        </StackPanel>
    </Grid>
</Window>
 
 
cs

 

  • MainWindow.xaml.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Threading;
using System.Windows.Threading;
namespace WpfBackGround
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private BackgroundWorker myBackgroundWorkerThread;
        private int number = 0;
 
        public MainWindow()
        {
            InitializeComponent();
 
            initBackgroundThread();
        }
 
        private void initBackgroundThread()
        {
            myBackgroundWorkerThread = new BackgroundWorker();
            myBackgroundWorkerThread.DoWork += doWork;
            myBackgroundWorkerThread.RunWorkerAsync();
        }
 
        private void btn_Click(object sender, RoutedEventArgs e)
        {
            txtLbl.Content = number.ToString();
        }
 
        private void doWork(object sender, DoWorkEventArgs e)
        {
            while (true)
            {
                Thread.Sleep(200);
 
                this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                    (ThreadStart)delegate()
                    {
                        txtBackLbl.Content = number.ToString();
 
                        number++;
                        if (number % 10000 == 0)
                        {
                        number = 0;
                        }
                    });
            }
        }
    }
}
 
 
cs

 

Reference

 

daegukdo/TIL

today I learned. Contribute to daegukdo/TIL development by creating an account on GitHub.

github.com

 

Thread(쓰레드)란 무엇인가?

우리가 흔히 사용하고 있는 OS는 '멀티 OS'라고 한다. 이것의 의미는 동시에 여러 가지 작업을 한다는 것을 뜻한다. MP3를 들으며 워드를 작성하면서 인터넷 서핑을 할 수 있다. 이때 각각의 응용

donghoson.tistory.com

 

[WPF] WPF에서 멀티쓰레드 프로그래밍을 하자! WPF에서 Thread 프로그래밍은 매우 쉽다!

프로그래밍에 대해 중급개발자, 초보개발자를 나누는 기준 중 하나가 멀티쓰레드 프로그래밍을 할 수 있는가입니다. 그 만큼 최근 프로그래밍할 때에는 멀티쓰레드가 매우 중요한 요소중 하나

eincs.com

 

WPF 멀티쓰레드 프로그래밍(Multi Thread), WPF에서 Dispatcher, BackgroundWorker를 이용한 멀티스레드 구현

WPF 멀티쓰레드 프로그래밍(Multi Thread), WPF에서 Dispatcher, BackgroundWorker를 이용한 멀티스레드 구현 이론/실습_WPF학원가기전에 한번 보세요~www.topcredu.co.kr 이종철1.5 WPF 멀티쓰레드 프로그래밍n 멀티

ojc.asia

 

반응형