実験のデータ収集系のソフトウェアでは、 発生するデータを収集しつつ記録媒体へ出力したり、コンソールから人間の 指示を受け取ったり、進行状況を表示したりしなければならないのが 一般的である。すなわち、複数の入出力を同時に扱う必要がある。 しかしながら入出力は通常システムによりブロックされる (完了するまでユーザプログラムに制御が戻らない)。従って、同時に複数の 入出力を扱おうとすると、一つ一つの入出力を平行に処理させる必要が出てくる。 これを実現するには、(現在では)通常 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(); } }