フォーム側の処理
まずフォームの interface 部の uses 節にスレッドのユニット名、この場合は Unit2 を追加します。これをしないとフォームからスレッドを呼び出す事ができません。ここに追加するのはクラス名ではなくユニット名、要するに保存したファイル名を記述するということに気をつけてください。
Form1 の uses 節 |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Unit2;
|
次に TMyThread 型の変数をフォームの private に宣言します。これはここで使用するスレッド、この場合だと数をカウントする
TMyThread というスレッドを MyThread と言う名前の変数で宣言していることになります。TButton 型のオブジェクトを
Button1 で宣言するのと同じです。
Form1 の定義 |
type
TForm1 = class(TForm)
Label1: TLabel;
Button1: TButton;
private
{ Private 宣言 }
MyThread : TMyThread;
public
{ Public 宣言 }
end;
|
これでつまらない準備作業は終わりです。次はボタンを押された場合の処理を記述します。ここでは
- 新しいスレッドを生成し、
- スレッド終了時のイベントと関連付け、
- 処理を実行させる
だけです。なんか難しそうですが書くものは単純です。
Form1 の Button1の OnClick |
procedure TForm1.Button1Click(Sender: TObject);
begin
//ここでスレッドを生成し、処理を実行している
MyThread := TMyThread.Create(False);
//スレッド終了時の処理を割り当てる
//MyThreadDone手続きはこの後でつくる
MyThread.OnTerminate := MyThreadDone;
end;
|
Create メソッドに False を渡すとスレッド生成後、すぐに主処理( Executeメソッド) を実行します。Execute
メソッドについては後述します。次はスレッド終了時のイベントを作成します。名前は MyThreadDone インベントです。このイベントでは生成したスレッドオブジェクトを破棄し、処理が終了したことをメッセージボックスで知らせます。このイベントはオブジェクトインスペクタから作ることができないので
procedure... から自分で記述します。
Form1 の MyThreadDone |
procedure TForm1.MyThreadDone(Sender: TObject);
begin
ShowMessage('End');
end;
|
手動で追加したイベントは Type中に宣言する必要がありますので追加します。
Form1 の定義 |
type
TForm1 = class(TForm)
Label1: TLabel;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
MyThread : TMyThread;
procedure MyThreadDone(Sender: TObject);
public
{ Public 宣言 }
end;
|
これでフォーム側のコードは完成です。フォーム側では使用するスレッドを準備し、ボタンを押す事によりそのスレッドを生成、実行するように定義しました。また、スレッドの処理が終了したらメッセージボックスを表示するように記述しました。
|
スレッド側の処理
次はスレッド側の処理です。TMyThread のコードを表示し、implemetation 部に uses
節を追加してください。ここにフォームのユニット名と SysUtils ユニットを追加します。
Unit2 の定義 |
unit Unit2;
interface
uses
Classes;
type
TMyThread = class(TThread)
private
{ Private 宣言 }
protected
procedure Execute; override;
end;
implementation
uses
Unit1, SysUtils;
|
次に1から1000までカウントする際に使用する内部変数を定義します。名前は FCount で整数型で宣言しましょう。
TMyThread の定義 |
type
TMyThread = class(TThread)
private
{ Private 宣言 }
FCount : integer;
protected
procedure Execute; override;
end;
|
そして Execute メソッドに処理を記述します。この
Execute メソッドはスレッドが生成された直後に実行されるメソッドです ( 引数により直後に実行するかどうかを指定できます
) 。処理内容は 1 から 1000 までカウントし、その途中経過をフォームのラベルに表示することでしたので・・・
TMyThread の実行内容 (これは間違い) |
procedure TMyThread.Execute;
var
i : integer;
begin
for i := 1 to 1000 do begin
Form1.Label1.Caption := IntToStr(i);
end;
end;
|
と、したいところでしょうが これは間違いです。この場合ラベル1というオブジェクトは直接、メインスレッドの状態を無視した他スレッドから操作されています。別スレッドはメインスレッドのオブジェクトを安全に操作する必要があります。では、安全にアクセスするにはどうしたらいいのでしょうか?それは、直接
VCL をアクセスする処理だけをメインスレッドに依頼するのです。それを実現するのが TThread クラスの Synchronize
メソッドです。次のコードは正しい例です。
TMyThread の実行内容 |
procedure TMyThread.Execute;
var
i : integer;
begin
for i := 1 to 1000 do begin
FCount := i;
Synchronize(CountUp);
end;
end;
procedure TMyThread.CountUp;
begin
//この処理はメインスレッドにより実行される
Form1.Label1.Caption := IntToStr(FCount);
end;
|
Synchronize メソッドには手続き名を引数で渡します。するとその手続きの処理だけはメインスレッド内部で処理されます。ですのでこの手続き内の処理を巨大なものにすると、マルチスレッドの意味が失われます。次の例は悪い例です。これだとカウントするループの処理は全てメインスレッドで処理されてしまいます。
TMyThread の実行内容 (これは失敗) |
procedure TMyThread.Execute;
begin
Synchronize( CountUp );
end
procedure TMyThread.CountUp;
var
i : integer;
begin
for i := 1 to 1000 do begin
Form1.Label1.Caption := IntToStr(i);
end;
end;
|
最後にこの手続き CountUp を TMyThread クラスのメソッドとして宣言する必要がありますので追加します。
TMyThread の定義 |
type
TMyThread = class(TThread)
private
{ Private 宣言 }
FCount : integer;
procedure CountUp;
protected
procedure Execute; override;
end;
|
|