WPF에서 멀티쓰레드 사용하기

yylog·2022년 9월 5일
0

파일을 내려받을 때 프로그래스바를 이용하여 현재 작업 진척률을 나타내고 싶었다.
이를 위해선 멀티쓰레드 프로그래밍이 필요한대 WPF에서 멀티쓰레드 프로그래밍을 하는 과정을 정리해본다.
아래 그림처럼 프로그래밍을 하고싶다..!

문제

파일을 내려받으면 UI가 멈춰버려 사용자가 어떤 버튼도 누를 수 없다
-> 작업과 UI 쓰레드를 분리시켜야한다.
파일을 내려받으면서 동시에 UI 안에 프로그래스바가 움직여야 한다.
-> 별도의 쓰레드로 작업을 비동기적으로 실행하면서 UI 쓰레드와 자동으로 동기화 해줘야한다.

Background Worker

  • 백그라운드에서 비동기적으로 실행된다.
  • System.ComponentModel 아래의 클래스로 코드를 별도의 쓰레드에서 동시에 비동기적으로 실행하게 해주고 기본(메인)쓰레드와 자동으로 동기화 해준다.
  • Background Worker 는 Thread Class 처럼 Thread를 직접 생성하는게 아니고 기존에 있던 Thread 풀에서 가져온 Thread를 사용한다.
    (C#에서 Thread를 수행 할 작업을 쓰레드 풀의 큐에 넣으면 내부적으로 기존에 생성되어있던 Thread를 가져와 작업을 수행 후 다시 대기 상태로 유지시킨다. CLR은 이 스레드풀에 최적의 스레드 개수가 유지되도록 관리해준다. -> 나중에 다시 공부해보자..!)
    - Background Worker를 사용하기 위한 메소드
    Dowork: 백그라운드 쓰레드가 할 일 을 기술
    ProcessedChanged: 작업에 대해 변경이 생길 때 호출
    RunWorkerCompleted: 작업이 완료되었을 때 무언가 할 수 있도록 지원
    ReportProgress: 메소드 UI와 백그라운드 작업이 통신할 수 있다.

생각(막혔던 부분 정리)

MVVM으로 구현을 해서 바인딩을 할 때 컨트롤의 ref 를 가져와야 뷰모델에서 바인딩이 가능했다.

소스코드

XAML

<Window x:Class="progressbar.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:progressbar"
        mc:Ignorable="d"
        Title="MainWindow" Height="150" Width="400">
    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter" />
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="40"/>
        </Grid.RowDefinitions>
        <Grid>
        <ProgressBar Name="pbStatus" Height="30" Margin="5,0,5,0" Value="{Binding CurrentProgress, Mode=OneWay}" Maximum="{Binding MaxValue, Mode=OneWay}"/>
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="{Binding ProgressVisibility, Converter={StaticResource booleanToVisibilityConverter}}">
            <TextBlock Text="{Binding CurrentProgress, StringFormat={}{0:0}}" HorizontalAlignment="Center" VerticalAlignment="Center" />
            <TextBlock Text="/" />
            <TextBlock Text="{Binding MaxValue, StringFormat={}{0:0}}"/>
        </StackPanel>
        </Grid>
        <Grid Grid.Row="1">
            <Button Width="100" Height="28" Margin="5" Content="Start" Command="{Binding StartWorkCommand}"/>
        </Grid>
    </Grid>
</Window>

CS

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
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;

namespace progressbar
{
    public partial class MainWindow : Window
    {
        MainWindowViewModel model;
        public MainWindow()
        {
            InitializeComponent();
            model = this.DataContext as MainWindowViewModel;
            
        }
    }
    public class MainWindowViewModel : ViewModelBase
    {
        MainWindowViewModel model;
        public MainWindowViewModel()
        {
            InitControl();
            InitProgressBar();
        }
        private BackgroundWorker _worker;

        private int _MaxValue;
        public int MaxValue { get { return _MaxValue; } private set { SetProperty(ref _MaxValue, value); } }

        private int _currentProgress;
        public int CurrentProgress { get { return _currentProgress; } private set { SetProperty(ref _currentProgress, value); } }

        private bool _progressVisibility;
        public bool ProgressVisibility { get { return _progressVisibility; } set { SetProperty(ref _progressVisibility, value); } }

        private ICommand _startWorkCommand;
        public ICommand StartWorkCommand { get { return _startWorkCommand ?? (_startWorkCommand = new RelayCommand(_worker.RunWorkerAsync, !_worker.IsBusy)); } }


        private void InitControl()
        {
            MaxValue = 100;
            ProgressVisibility = false;
        }

        private void InitProgressBar()
        {
            _worker = new BackgroundWorker()
            {
                WorkerSupportsCancellation = true
            };
            _worker.DoWork += DoWork;
            _worker.WorkerReportsProgress = true;
            _worker.ProgressChanged += ProgressChanged;
        }

        private void DoWork(object sender, DoWorkEventArgs e)
        {
            ProgressVisibility = true;
            CurrentProgress = 1;
            for (int i = 0; i <= MaxValue; i++)
            {
                Thread.Sleep(50);
                (sender as BackgroundWorker).ReportProgress(i);

            }

        }

        private void ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            CurrentProgress = e.ProgressPercentage;
        }

    }


    #region RelayCommand
    public class RelayCommand : ICommand
    {
        readonly Action<object> _execute;
        readonly Predicate<object> _canExecute;

        /// <summary>
        /// Creates a new command that can always execute.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        public RelayCommand(Action<object> execute)
            : this(execute, null)
        {
        }

        /// <summary>
        /// Creates a new command.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        /// <param name="canExecute">The execution status logic.</param>
        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");

            _execute = execute;
            _canExecute = canExecute;
        }
        /// <summary>
        /// Creates a new command.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        /// <param name="canExecute">The execution status logic.</param>
        public RelayCommand(Action<object> execute, bool v)
            : this(execute, null)
        {
        }


        [DebuggerStepThrough]
        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute(parameter);
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute(object parameter)
        {
            _execute.Invoke(parameter);
        }
    }
    #endregion

    #region ViewModelBase
    public class ViewModelBase : INotifyPropertyChanged, IDisposable
    {

        public virtual string DisplayName { get; protected set; }

        public event PropertyChangedEventHandler PropertyChanged;
        
        public virtual void OnPropertyChanged(string name)
        {
            this.VerifyPropertyName(name);
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
            }
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
        public bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
        {
            if (Equals(storage, value))
            {
                return false;
            }

            storage = value;
            this.OnPropertyChanged(propertyName);
            return true;
        }

        public void Dispose()
        {

        }
        protected virtual void OnDispose()
        {
        }
    }
    #endregion
}

출처

http://ojc.asia/bbs/board.php?bo_table=WPF&wr_id=23
https://afsdzvcx123.tistory.com/entry/4%EC%9E%A5-WPF-%EB%A9%80%ED%8B%B0%EC%93%B0%EB%A0%88%EB%93%9C-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D

profile
경험하고 공부한 모든 것을 기록하는 공간

0개의 댓글