[C#] 61. ウィンドウフォーム(Window form)でスレッド(Thread)を使い方、クロススレッド問題解決
こんにちは。明月です。
この投稿はC#のウィンドウフォーム(Window form)でスレッド(Thread)を使い方、クロススレッド問題解決に関する説明です。
以前の投稿でC#のスレッドに関して説明したことがあります。
link - [C#] 37. スレッド(Thread)を使い方、Thread.Sleep関数を使い方
スレッドはプログラム内で並列処理することの意味です。まずウィンドウはシングルスレッドの無限ループで動いています。
でも、我々がボタンのクリックイベントでループを実行するロジックを作りましょう。
using System;
using System.Windows.Forms;
using System.Threading;
namespace WindowsFormsApp
{
// Formクラスを継承
public partial class Form1 : Form
{
// メンバー変数ボタン
private Button button1 = null;
// コントロールクラスの初期設定関数
private T setInitControl<T>(T ctl, string name, int point) where T : Control
{
// 位置設定
button.Location = new System.Drawing.Point(27, point);
// コントロールの名設定
button.Name = name;
// コントロールのサイズ設定
button.Size = new System.Drawing.Size(75, 23);
// タブのIndex設定
ctl.TabIndex = 0;
// リターン
return ctl;
}
// コンストラクタ
public Form1()
{
// 初期化
InitializeComponent();
// ボタンインスタンス生成後、初期設定
this.button1 = setInitControl(new Button(), "Button1", 40);
// ボタンのText設定
this.button1.Text = "Button";
// フォームにControl追加
this.Controls.Add(button1);
// イベント追加(ラムダ式で追加)
this.button1.Click += (sender, e) =>
{
// 0から9999までループ
for (int i = 0; i < 10000; i++)
{
// コンソールに出力
Console.WriteLine(i);
// スレッド待機1秒
Thread.Sleep(1000);
}
};
}
}
}
プログラムを実行してボタンを押下するとループが終わるまでウィンドウフォームは動きません。時間が流れたら応答なしになってプログラムが凍っている時もあります。
つまり、ウィンドウはシングルスレッド状況なのでその関数のスタックに掛けると処理が終わるまで動きません。
そうするとボタンを押下する時、複雑な処理をすると思えばどのように処理するでしょう?スレッドを利用すれば良いでしょう。
using System;
using System.Windows.Forms;
using System.Threading;
namespace WindowsFormsApp
{
// Formクラスを継承
public partial class Form1 : Form
{
// メンバー変数ボタン
private Button button1 = null;
// コントロールクラスの初期設定関数
private T setInitControl<T>(T ctl, string name, int point) where T : Control
{
// 位置設定
button.Location = new System.Drawing.Point(27, point);
// コントロールの名設定
button.Name = name;
// コントロールのサイズ設定
button.Size = new System.Drawing.Size(75, 23);
// タブのIndex設定
ctl.TabIndex = 0;
// リターン
return ctl;
}
// コンストラクタ
public Form1()
{
// 初期化
InitializeComponent();
// ボタンインスタンス生成後、初期設定
this.button1 = setInitControl(new Button(), "Button1", 40);
// ボタンのText設定
this.button1.Text = "Button";
// フォームにControl追加
this.Controls.Add(button1);
// イベント追加(ラムダ式で追加)
this.button1.Click += (sender, e) =>
{
// スレッドプール生成
ThreadPool.QueueUserWorkItem((_) =>
{
// 0から9999までループ
for (int i = 0; i < 10000; i++)
{
// コンソールに出力
Console.WriteLine(i);
// スレッド待機1秒
Thread.Sleep(1000);
}
});
};
}
}
}
ボタンをクリックしてコンソールに1ずつに出力してもウィンドウが凍らないことを確認できます。
そうすると今回はコンソールに出力することではなく、ウィンドウのコントロールで出力するように作成しましょう。
using System;
using System.Windows.Forms;
using System.Threading;
namespace WindowsFormsApp
{
// Formクラスを継承
public partial class Form1 : Form
{
// メンバー変数ボタン
private Button button1 = null;
private Label label1 = null;
// コントロールクラスの初期設定関数
private T setInitControl<T>(T ctl, string name, int point) where T : Control
{
// 位置設定
button.Location = new System.Drawing.Point(27, point);
// コントロールの名設定
button.Name = name;
// コントロールのサイズ設定
button.Size = new System.Drawing.Size(75, 23);
// タブのIndex設定
ctl.TabIndex = 0;
// リターン
return ctl;
}
// コンストラクタ
public Form1()
{
// 初期化
InitializeComponent();
// ボタンインスタンス生成後、初期設定
this.button1 = setInitControl(new Button(), "Button1", 80);
// ラベルインスタンス生成後、初期設定
this.label1 = setInitControl(new Label(), "Label1", 40);
// ボタンのText設定
this.button1.Text = "Button";
// フォームにControl追加
this.Controls.Add(button1);
this.Controls.Add(label1);
// イベント追加(ラムダ式で追加)
this.button1.Click += (sender, e) =>
{
// スレッドプール生成
ThreadPool.QueueUserWorkItem((_) =>
{
// 0から9999までループ
for (int i = 0; i < 10000; i++)
{
// Labelのテキスト設定
this.label1.Text = $"{i}";
// スレッド待機1秒
Thread.Sleep(1000);
}
});
};
}
}
}
ボタンをクリックするとすぐエラーが発生します。
理由はWindowで動いているスレッドとスレッドプールで動いているスレッドが同期化されてないからです。
スレッド間に同期化しようと思えば、お互いにlockを設定して同期化すればよいのに、ウィンドウメッセージを動いているスレッドにlockを掛ける方法がありません。
これをC#ウィンドウ開発ではクロススレッド問題と言います。
これを解決する方法が各コントロールにあるInvoke関数を利用してvisitorパターン、つまりコールバック関数という方法で処理ができます。
using System;
using System.Windows.Forms;
using System.Threading;
namespace WindowsFormsApp
{
// 拡張関数ためのクラス
public static class Util
{
// 拡張関数
public static void InvokeControl(this Control ctl, Action func)
{
// Thread ID比較
if (ctl.InvokeRequired)
{
// デリゲート関数で実行
ctl.Invoke(func);
}
else
{
// Thread IDが同じならそのまま実行(同じスレッドという意味)
func();
}
}
}
// Formクラスを継承
public partial class Form1 : Form
{
// メンバー変数ボタン
private Button button1 = null;
private Label label1 = null;
// コントロールクラスの初期設定関数
private T setInitControl<T>(T ctl, string name, int point) where T : Control
{
// 位置設定
button.Location = new System.Drawing.Point(27, point);
// コントロールの名設定
button.Name = name;
// コントロールのサイズ設定
button.Size = new System.Drawing.Size(75, 23);
// タブのIndex設定
ctl.TabIndex = 0;
// リターン
return ctl;
}
// コンストラクタ
public Form1()
{
// 初期化
InitializeComponent();
// ボタンインスタンス生成後、初期設定
this.button1 = setInitControl(new Button(), "Button1", 80);
// ラベルインスタンス生成後、初期設定
this.label1 = setInitControl(new Label(), "Label1", 40);
// ボタンのText設定
this.button1.Text = "Button";
// フォームにControl追加
this.Controls.Add(button1);
this.Controls.Add(label1);
// イベント追加(ラムダ式で追加)
this.button1.Click += (sender, e) =>
{
// スレッドプール生成
ThreadPool.QueueUserWorkItem((_) =>
{
// 0から9999までループ
for (int i = 0; i < 10000; i++)
{
// デリゲート関数を通ってウィンドウスレッドから下記の関数を呼び出すことにする。
this.label1.InvokeControl(() =>
{
// Labelのテキスト設定
this.label1.Text = $"{i}";
});
// スレッド待機1秒
Thread.Sleep(1000);
}
});
};
}
}
}
ソース上にstatic Utilクラスを作成してControlクラスの拡張関数を作成しました。そしてスレッドプールの中でlabel1インスタンスにInvokeControl関数を呼び出してラムダ式でコールバック関数を作成しました。
ボタンをクリックするとLabelに数字が1秒単位で更新することを確認できます。
ここまでC#のウィンドウフォーム(Window form)でスレッド(Thread)を使い方、クロススレッド問題解決に関する説明でした。
ご不明なところや間違いところがあればコメントしてください。
- [C#] 61. ウィンドウフォーム(Window form)でスレッド(Thread)を使い方、クロススレッド問題解決2021/11/04 19:29:51
- [C#] 60. ウィンドウフォーム(Window form)のイベント設定する方法2021/11/02 21:18:08
- [C#] 59. ウィンドウフォーム(Window form)にコントロール(Control)を使い方法2021/10/29 19:45:43
- [C#] 58. ウィンドウフォーム(Window form)を作成する方法、そしてウィンドウメッセージとキュー2021/10/27 20:35:44
- [C#] 57. コーティング規約2021/10/21 18:57:02
- [C#] 56. 値の初期化及び基本データ値(default)を設定する方法、そして原始データのnull処理、?と??の使い方2021/10/21 18:54:41
- [C#] 55.namespaceとusing、そしてpartialの使い方2021/10/21 18:51:39
- [C#] 54. Reflection機能を使い方 - Attribute2021/10/20 19:29:31
- check2024/04/10 19:03:53
- [Java] 64.Spring bootとReactを連結する方法(Buildする方法)2022/03/25 21:02:18
- [Javascript] Node.jsをインストールしてReactを使う方法2022/03/23 18:01:34
- [Java] 63. Spring bootでcronスケジューラとComponentアノテーション2022/03/16 18:57:30
- [Java] 62. Spring bootでWeb-Filterを設定する方法(Spring Security)2022/03/15 22:16:37
- [Java] JWT(Json Web Token)を発行、確認する方法2022/03/14 19:12:58
- [Java] 61. Spring bootでRedisデータベースを利用してセッションクラスタリング設定する方法2022/03/01 18:20:52
- [Java] 60. Spring bootでApacheの連結とロードバランシングを設定する方法2022/02/28 18:45:48
- [Java] 59. Spring bootのJPAでEntityManagerを使い方2022/02/25 18:27:48
- [Java] 58. EclipseでSpring bootのJPAを設定する方法2022/02/23 18:11:10
- [Java] 57. EclipseでSpring bootを設定する方法2022/02/22 19:04:49
- [Python] Redisデータベースに接続して使い方2022/02/21 18:23:49
- [Java] Redisデータベースを接続して使い方(Jedisライブラリ)2022/02/16 18:13:17
- [C#] Redisのデータベースを接続して使い方2022/02/15 18:46:09
- [CentOS] Redisデータベースをインストールする方法とコマンドを使い方2022/02/14 18:33:07