Interlocked.CompareExchange でスレッドセーフに数値比較する

下記のページで、スレッドセーフに数値比較する Interlocked.CompareExchange の疑似コードを紹介しました。

Interlocked.CompareExchange の疑似コード

スレッドセーフに変数の数値比較を実施する場合、Interlocked.CompareExchange というメソッドを利用する必要がありますが、MSDNに書いてある引数の解説が意味不明です。…

では、以上のページをふまえて実際に Interlocked.CompareExchange を利用してみます。

下記のようなサンプルを作ってみましょう。ボタンを押したらスレッドを開始してグローバルな変数の数値を評価して ゼロのときはコントロール色、ノンゼロのときは水色 ( Color.Aqua ) でウインドウの色を変更します。

メインフォームのコード ( Form1.cs )

Form1 に button1 と button2 を配置してください。

Form1 に Load のイベントハンドラを準備してください。
Form1 に Closing のイベントハンドラを準備してください。
Form1 に Paint のイベントハンドラを準備してください。

button1 に button1_Click のイベントハンドラを準備してください。
button2 に button2_Click のイベントハンドラを準備してください。

button1 を押すと、フォームの色が水色に点滅します。button2 を押すと点滅が止まります。

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

namespace aaa
{
	public partial class Form1 : Form
	{

		Thread TheThread;
		ParametersForThread Pft;

		public Form1()
		{
			InitializeComponent();
		}

		private void Form1_Load( object sender, EventArgs e )
		{

			button1.Text = "スレッド開始";
			button2.Text = "スレッド終了";

			Pft = new ParametersForThread();
			Pft.FlagThreadExit = false;
			Pft.TheForm = this;
			Pft.TheValue = 0;

		}

		private void Form1_FormClosing( object sender, FormClosingEventArgs e )
		{

			if ( TheThread != null )
			{
				if ( TheThread.IsAlive )
				{
					MessageBox.Show( "スレッドを終了させてからウインドウを閉じてください." );
					e.Cancel = true;
				}
			}

		}

		private void Form1_Paint( object sender, PaintEventArgs e )
		{

			if ( Pft != null )
			{

				// 評価対象の値が 1 かどうか調べる.
				int ret = Interlocked.CompareExchange( ref( Pft.TheValue ), 1, 1 );
				if ( ret != 0 )
				{
					this.BackColor = Color.Aqua;
				}
				else
				{
					this.BackColor = SystemColors.Control;
				}

				/*
				// ret という変数を省略したかったらこう書く.
				if ( Interlocked.CompareExchange( ref ( Pft.TheValue ), 1, 1 ) != 0 )
				{
					this.BackColor = Color.Aqua;
				}
				else
				{
					this.BackColor = SystemColors.Control;
				}
				*/

				// タイトルバーに表示する.
				this.Text = Pft.TheValue.ToString();

			}

		}

		public delegate void DelegateXxx();
		public void FormRefresh()
		{
			this.Refresh();
		}

		private void button1_Click( object sender, EventArgs e )
		{

			if ( TheThread != null )
			{
				if ( TheThread.IsAlive )
				{
					MessageBox.Show( "いったんスレッドを終了させてください." );
					return;
				}
			}

			ParameterizedThreadStart pts = new ParameterizedThreadStart( ThisProject.Worker );
			TheThread = new Thread( pts );
			TheThread.Start( Pft );

		}

		private void button2_Click( object sender, EventArgs e )
		{
			if ( Pft != null )
			{
				Pft.FlagThreadExit = true;
			}
		}
	}
}

82~86行目にあるコードをお忘れなく。このコードは外部のスレッドからこのウインドウのGUIにアクセスするときに必要なデリゲートです。

public delegate void DelegateXxx();
public void FormRefresh()
{
	this.Refresh();
}

スレッドのコード ( ThisProject.cs )

スレッドでは変数を 0 と 1 交互に変化させます。変化させるインターバルは MSEC_INTERVAL という定数で定義しています。高速に点滅させたければ定数を小さくして、ゆっくり点滅させたければ定数を大きくしてください。

FlagThreadExit という変数が true になったらスレッドを脱出します。

using System;
using System.Threading; // これをお忘れなく!.
using System.Windows.Forms;

namespace aaa
{

	// スレッドに与える引数のクラス.
	public class ParametersForThread
	{
	
		public bool FlagThreadExit;

		public Form TheForm;
		public int TheValue;

		public ParametersForThread()
		{
			FlagThreadExit = false;

			TheForm = null;
			TheValue = 0;
		}

	}

	public class ThisProject
	{
	
		public static void Worker( object parameter )
		{

			// パラメータをキャストして取得する.
			ParametersForThread prm = ( ParametersForThread )( parameter );

			DateTime dtm_prv = new DateTime( 1, 1, 1 );

			const int MSEC_INTERVAL = 500;

			// 無限ループ.
			for (;;)
			{

				// 終了フラグを検知したかどうか.
				if ( prm.FlagThreadExit )
				{
					// フラグを戻しておく.
					prm.FlagThreadExit = false;

					// スレッド脱出.
					return;
				}

				// 前回の時間との差分を取得する.
				DateTime dtm_now = DateTime.Now;
				TimeSpan tsp = ( dtm_now - dtm_prv );

				// インターバルよりも長いかどうか.
				if ( tsp.TotalMilliseconds > MSEC_INTERVAL )
				{

					// 前回の時間を更新する.
					dtm_prv = dtm_now;

					// 0 と 1 を交互に代入する.
					if ( Interlocked.CompareExchange( ref( prm.TheValue ), 1, 1 ) != 0 )
					{
						// 0 を代入する.
						Interlocked.Exchange( ref ( prm.TheValue ), 0 );
					}
					else
					{
						// 1 を代入する.
						Interlocked.Exchange( ref ( prm.TheValue ), 1 );
					}

					// 再描画を要請する.
					if ( prm.TheForm != null )
					{
						prm.TheForm.Invoke( new Form1.DelegateXxx( prm.TheForm.Refresh ));
					}

				}

				// メッセージ処理のすきま.
				Thread.Sleep(1);

			}

		}

	}
}