Interlocked.CompareExchange でスレッドセーフに数値比較する
下記のページで、スレッドセーフに数値比較する Interlocked.CompareExchange の疑似コードを紹介しました。
では、以上のページをふまえて実際に 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.Threading; をお忘れなく。
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);
}
}
}
}