【C#】【WPF】【Threadクラス】スレッドを起動して非同期処理を行う

前提知識

・スレッドには2種類がある。「UIスレッド」と「ワーカースレッド」
・「UIスレッド」は、ボタン押下等で起動するスレッド
・「ワーカースレッド」は、ThreadクラスのStartメソッド等で起動するスレッド

Threadクラスについて

1.非同期処理を実現するためのクラス

2.非同期処理を実現するための複数ある内の一つ。複数ある内で一番古い実現方法
・Threadクラス(.NET Framework1.1以降)
・ThreadPoolクラス(.NET Framework1.1以降)
・BackGroundWorkerクラス(.NET Framework2.0以降)
・Taskクラス(.NET Framework4.0以降)
・Taskクラスとasync/await(.NET Framework4.5以降)

3.ThreadクラスのStartメソッドの引数に指定するのは、スレッドとして起動したいメソッド。
 このメソッドは戻り値無し(void)のみ許されている

4.以下のデメリットがあるため、可能ならばThreadクラスは使用しない
┗通常、ThreadクラスのStartメソッドで起動するスレッド(ワーカースレッド)からは、
 UIのコントロールにアクセス不可
┗「デリゲート」を用いれば、アクセス可
┗「デリゲート」を用いて記載した処理は、UIスレッドで処理される
 (一旦ワーカースレッドが中断され、UIスレッドに戻る)
┗スレッド内に、UIのコントロールにアクセスする箇所が複数ある場合、複数個所に
 「デリゲート」の記載が必要
┗「デリゲート」は本来行いたい処理とは関係無い。関係無い記載があちこちにあるのは
  可読性を悪化させる

5.「デリゲート」を用いなくとも、コントロールにバインド済みの変数の値を更新することに
  より、バインド変数を介してコントロールへアクセスすることができる

6.一番新しい実現方法はTaskクラスを用いた方法であるため、可能ならばTaskクラスを
 使用する

サンプルプログラム①の概要

・「スレッド実行していません」を押すとスレッド処理を開始する
・「中断」を押すとスレッド処理を停止する。押されるまではスレッド内のループ処理を繰り返す
・中断後、スレッド内で「デリゲート」を用いてコントロールへアクセスする

サンプルプログラム①の画面

●初期画面


●「実行」ボタンを押した後の画面

サンプルプログラム①

public partial class MainWindow : Window
{
    private Thread t;
    private bool isSuspension;

    public MainWindow()
    {
        InitializeComponent();
    }

    /// ボタン押下時処理
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        if (Button1.Content.Equals("実行"))
        {
            t = new Thread(new ThreadStart(SampleThreadAsync));
            isSuspension = false;

            //コントロールを更新
            Label_1.Content = "スレッド実行中!";
            TextBox_1.Text = "スレッド実行中!";
            Button1.Content = "中断";

            t.Start();

        } else if(Button1.Content.Equals("中断"))
        {
            isSuspension = true;
            t = null;

            //コントロールを更新
            Label_1.Content = "スレッド実行していません";
            TextBox_1.Text = "スレッド実行していません";
            Button1.Content = "実行";
        }
    }

    /// スレッド処理用メソッド
    private void SampleThreadAsync()
    {
        double count;
        count = 0;

        while (isSuspension == false)
        {
            //中断ボタンが押されるまで繰り返し実行する処理
            Thread.Sleep(1000);
            count++;
        }

        //デリゲート
        this.Dispatcher.Invoke((Action)(() => {
            //この中の記載はUIスレッドとして処理される
            //スレッドからコントロールを更新
            Label_2.Content = "スレッド内のループの回数は" + count;
        }));
    }
}

実行結果①

●「中断」ボタンを押した後の画面

サンプルプログラム②のポイント

1.バインドさせるためのクラス「ApplicationViewModel」を作成する
・変数が更新された場合、UIへ通知するように設定する
┗「INotifyPropertyChanged」を継承
┗プロパティ「PropertyChangedEventHandler」を定義
┗メソッド「OnPropertyChanged」を定義
┗セッター内で「this.OnPropertyChanged(“XXXX”)」を実行

2.UIのコントロールと変数をバインドさせる
┗MainWindow.xamlにバインドの設定をする
┗MainWindow.xaml.csにバインドの設定をする

3.ワーカースレッド内で、バインドした変数を更新する

サンプルプログラム②

●バインドさせるためのクラス「ApplicationViewModel」

using System.ComponentModel;

namespace sampleApp
{
    public class ApplicationViewModel : INotifyPropertyChanged
    {
        private string _LabelText;

        public string LabelText
        {
            get
            {
                return _LabelText;
            }
            set
            {
                _LabelText = value;
                this.OnPropertyChanged("LabelText");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged = null;
        protected void OnPropertyChanged(string info)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
        }
    }
}

●UIのコントロールと変数をバインドさせる(MainWindow.xaml)

<Window x:Class="sampleApp.MainWindow"
        (省略)
        Title="MainWindow" Height="176" Width="338" ResizeMode="NoResize">
    <Grid>
    (省略)
        <Label x:Name="Label_2" Content="{Binding LabelText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Margin="15,100,0,0" VerticalAlignment="Top" Width="310" Height="26"/>
    </Grid>

●UIのコントロールと変数をバインドさせる(MainWindow.xaml)
 ワーカースレッド内で、バインドした変数を更新する

public partial class MainWindow : Window
{
    private Thread t;
    private bool isSuspension;
    private ApplicationViewModel applicationViewModel;

    public MainWindow()
    {
        InitializeComponent();

        //コントロールと変数をバインド
        applicationViewModel = new ApplicationViewModel();
        applicationViewModel.LabelText = "スレッド内のループの回数はXX";
        Label_2.DataContext = applicationViewModel;
    }

    /// ボタン押下時処理
    private void Button_1_Click(object sender, RoutedEventArgs e)
    {
        if (Button_1.Content.Equals("実行"))
        {
            t = new Thread(new ThreadStart(SampleThreadAsync));
            isSuspension = false;

            //コントロールを更新
            Label_1.Content = "スレッド実行中!";
            TextBox_1.Text = "スレッド実行中!";
            Button_1.Content = "中断";

            t.Start();

        }
        else if (Button_1.Content.Equals("中断"))
        {
            isSuspension = true;
            t = null;

            //コントロールを更新
            Label_1.Content = "スレッド実行していません";
            TextBox_1.Text = "スレッド実行していません";
            Button_1.Content = "実行";
        }
    }

    /// スレッド処理用メソッド
    private void SampleThreadAsync()
    {
        double count;
        count = 0;

        while (isSuspension == false)
        {
            //中断ボタンが押されるまで繰り返し実行する処理
            Thread.Sleep(1000);
            count++;
        }
        //デリゲートを用いずに、バインド済みの変数を介してスレッドからコントロールを更新
        applicationViewModel.LabelText = "スレッド内のループの回数は" + count + "回(デリゲートを用いずに更新)";
    }
}

実行結果②

●「中断」を押した後の画面

タイトルとURLをコピーしました