実験のデータ収集系のソフトウェアでは、 発生するデータを収集しつつ記録媒体へ出力したり、コンソールから人間の 指示を受け取ったり、進行状況を表示したりしなければならないのが 一般的である。すなわち、複数の入出力を同時に扱う必要がある。 しかしながら入出力は通常システムによりブロックされる (完了するまでユーザプログラムに制御が戻らない)。従って、同時に複数の 入出力を扱おうとすると、一つ一つの入出力を平行に処理させる必要が出てくる。 これを実現するには、(現在では)通常 thread を用いる。
C# の Thread class は、Java の Thread class とは若干思想が異なる(ように 筆者には思える)。Java の Thread class は public void run() method に thread として実行すべきプログラムを書き、start() method により起動する。 一方 C# の方は、実行すべき method を Thread class の外から与える。その ためには、method への参照が必要となるが、これを ThreadStart という delegate で与える。delegate というのは、要するに関数への参照である。
例を示そう。
using System;
using System.Threading;
public class Work
{
public static void Do()
{
for(int i = 0; i < 3; i++)
{
Console.WriteLine("In Work thread [" + i + "]" );
Thread.Sleep(100);
}
}
}
class ThreadTest
{
public static void Main()
{
Thread myThread = new Thread(new ThreadStart(Work.Do));
myThread.Start();
Console.WriteLine("Waiting...");
}
}
この例では、static method である Do() を渡しているので、 Work class の object を生成する必要はなく、どちらかと 言えば C の書き方に似ている。 ThreadStart として渡すのは、object の member method でもよい。
上記プログラムは Console Application であったが、Graphical User Interface と組み合わせてみる。Data 収集プログラムの例は少し複雑になるので別に示すことに して、ここでは data 収集プログラムを意識しつつ 前に例示した Graphical Program を少し改造してみよう。プログラム仕様は以下の通りとする。
上記仕様を満たすために、ある決まった大きさになるまで円の半径を 0.5 秒毎に 変える部分を子 thread とし、それ以外を main の thread で扱うことにする。 このようにしなければならない理由は無いが、例題として、元のプログラムの形を できるだけ保って、thread として付け加えた部分をできるだけわかりやすくする ためである。
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
public class MyApp : Form
{
private class MyPanel : Panel
{
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Brush [] mybrush = {Brushes.Red, Brushes.Green, Brushes.Blue};
Graphics g = e.Graphics;
Rectangle r = this.ClientRectangle;
int xc = (r.Left + r.Right) / 2;
int yc = (r.Top + r.Bottom) / 2;
int rd = (dm + 1) / 2;
g.FillEllipse( mybrush[brushindex], xc - rd, yc - rd, dm, dm);
}
}
static bool running = false;
static int dm;
static int delta;
static int brushindex = 0;
static MyPanel mypanel = new MyPanel();
static Button [] mybutton = {null, null, null};
static Label mylabel = new Label();
static String [] btntitle = { "色", "拡大", "縮小" };
static String [] clrtitle = { "赤", "緑", "青" };
Thread t1;
static void DiamChg()
{
running = true;
if( delta < 0 )
{
while( dm > 30 )
{
dm += delta;
mypanel.Invalidate();
Thread.Sleep(500);
}
}
else if( delta > 0 )
{
while( dm < 300 )
{
dm += delta;
mypanel.Invalidate();
Thread.Sleep(500);
}
}
running = false;
}
public MyApp()
{
Text = "コントロール付";
dm = 150;
Panel mybuttons = new Panel();
mybuttons.Dock = DockStyle.Top;
mybuttons.BorderStyle = BorderStyle.Fixed3D;
mybuttons.Size = new Size(320, 24);
int i;
for( i = 0; i < 3; i++ )
{
mybutton[i] = new Button();
mybutton[i].Dock = DockStyle.None;
mybutton[i].Text = btntitle[i];
mybutton[i].Size = new Size(80, 20);
mybutton[i].Location = new Point(i * 80, 0);
mybutton[i].Click += new System.EventHandler(mybutton_Click);
mybuttons.Controls.Add(mybutton[i]);
}
mylabel.Dock = DockStyle.Bottom;
mylabel.BorderStyle = BorderStyle.Fixed3D;
mylabel.Location = new Point(0, 223);
mylabel.Size = new Size(320, 15);
mylabel.Text = clrtitle[0];
mypanel.Dock = DockStyle.Fill;
mypanel.BorderStyle = BorderStyle.Fixed3D;
mypanel.Location = new Point(0, 15);
mypanel.Size = new Size(320, 160);
mypanel.BackColor = Color.White;
AutoScaleBaseSize = new Size(5, 13);
ClientSize = new Size(320, 240);
Controls.AddRange(new Control[] {mybuttons, mylabel, mypanel});
}
public static void Main()
{
Application.Run(new MyApp());
}
private void mybutton_Click(object sender, EventArgs evArgs)
{
if(sender == mybutton[2])
{
if( !running )
{
delta = -10;
t1 = new Thread(new ThreadStart(DiamChg));
t1.Start();
}
}
else if(sender == mybutton[1])
{
if( !running )
{
delta = 10;
t1 = new Thread(new ThreadStart(DiamChg));
t1.Start();
}
}
else
{
if( brushindex == 2 )
brushindex = 0;
else
brushindex += 1;
mylabel.Text = clrtitle[brushindex];
mypanel.Invalidate();
}
}
protected override void OnSizeChanged(System.EventArgs e)
{
base.OnSizeChanged(e);
mypanel.Invalidate();
}
}