C# Quick Thread Programming

Copyright © 2003, Hirofumi Fujii
(LastUpdate: 03-Aug-2003)

はじめに

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


Graphical User Interface と組み合わせる

上記プログラムは Console Application であったが、Graphical User Interface と組み合わせてみる。Data 収集プログラムの例は少し複雑になるので別に示すことに して、ここでは data 収集プログラムを意識しつつ 前に例示した Graphical Program を少し改造してみよう。プログラム仕様は以下の通りとする。

Data 収集プログラムでは、「拡大」「縮小」は例えば 「data 収集開始」「data 収集停止」ボタン、 「色」ボタンは「現在値モニタ」などに対応すると考えると data 収集プログラムのモデルとして考えやすいだろう。

上記仕様を満たすために、ある決まった大きさになるまで円の半径を 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();
  }
}