[All]
無名メソッドとスレッド
By: Yusuke Konno
Abstract: Delphi 2009で新たに搭載された言語機能である無名メソッドの具体的な活用例を紹介します。
Delphi 2009ではUnicode化が大きなトピックですが、同時に言語機能の強化も行われています。無名メソッド(匿名メソッド、Anonymous Method)はその1つです。無名メソッドは名前の通り名前のない関数・手続きで、これを利用することでクロージャーの実現や、高階関数の実現などが可能になります。
しかし、クロージャーや高階関数はDelphiユーザーにとってはそれほど馴染みのあるものではありません。しかし、無名メソッドはスレッドで用いるケースがあります。今回は無名メソッドをスレッド内で利用するという最も身近な活用方法について解説を行います。
スレッドにおける従来のコード(Delphi 2007まで)
Synchronizeメソッド
ワーカースレッドの処理中にメインスレッドのコンポーネント等にアクセスしたい時は、Synchronizeメソッドを使う必要があります。なぜなら、VCLのビジュアルコンポーネントはメインスレッドからアクセスしなければならないからです。Synchronizeメソッドを使わずにワーカースレッド内からメインスレッドのコンポーネント等にアクセスしようとすると、複数のスレッドから同時にアクセスされて、コンポーネントを破壊してしまう可能性があります。
以下の例は従来のスレッドにおけるSynchronizeメソッドを用いたコードです。
スレッドオブジェクトのユニット:OldThread.pas
unit
OldThread;
interface
uses
Classes, SysUtils;
type
TOldThread = class
(TThread)
private
FResultInt: Integer;
protected
procedure
Execute; override
;
procedure
SyncMethod;
end
;
implementation
uses
Unit1;
procedure
TOldThread.Execute;
var
i: Integer;
A,B: Integer;
begin
for
i := 0 to
99 do
begin
A := Random(10000);
B := Random(10000);
FResultInt := A - B;
Synchronize(SyncMethod);
end
;
end
;
procedure
TOldThread.SyncMethod;
begin
Form1.Memo1.Lines.Add('OT : '
+FormatFloat('#,##0'
,FResultInt));
end
;
end
.
メインフォームのユニット:Unit1.pas(一部)
uses
....., OldThread;
type
TForm1 = class
(TForm)
public
OT: TOldThread;
procedure
EndOT(Sender: TObject);
end
;
procedure
TForm1.Button1Click(Sender: TObject);
begin
Memo1.Clear;
Button1.Enabled := False;
OT := TOldThread.Create(False);
OT.FreeOnTerminate := True;
OT.OnTerminate := EndOT;
end
;
procedure
TForm1.EndOT(Sender: TObject);
begin
Button1.Enabled := True;
end
;
procedure
TForm1.FormCreate(Sender: TObject);
begin
Randomize;
end
;
実行結果

AとBに0から9999のランダムな整数値を代入し、A-Bという計算を行い、その結果をForm1のMemoコンポーネントに追加という作業を100回繰り返しています。
Synchronizeメソッドの問題点
Synchronizeメソッドの引数に渡す手続きは、何も引数を取ることが出来ないという制限があります。Memoコンポーネントはメインスレッド上にあるため、計算結果の追加に際してはSynchronizeメソッドを使わなければなりませんが、上記制限によってExecuteメソッド内のローカル変数を直接Synchronizeメソッドの引数に指定した手続きに渡すことが出来ません。これを解決するため、通常はスレッドクラスにプライベートフィールドを宣言し、そこに計算結果を保存することでSynchronizeメソッドの引数に指定した手続きから参照できるようになります。
上記コードではprivateスコープにFResultIntというInteger型の変数を媒介にして、SyncMethodにA-Bの計算結果を渡しています。この例では追加するフィールドが1つであるため、さして問題がないようにも思えますが、渡したいデータが増加するとその分フィールドを増やさなければならないため、コードが分かり辛くなります。また、そもそもFResultIntの存在自体が無駄でもあります。
無名メソッドを使う方法
無名メソッドとは?
無名メソッドはDelphi 2009で導入された言語機能の1つです。無名メソッドは以下のように型宣言することができます。
type
TAnonymousMethod = reference to
procedure
なにやら見慣れない型宣言ですね。これだけでは分かり辛いので、実際に変数として宣言して使用した例を見てみましょう。
type
TIntAM = reference to
procedure
(n: Integer);
var
IAM: TIntAM;
begin
IAM := procedure
(n: Integer)
begin
ShowMessage(FormatFloat('#,##0'
,n));
end
;
IAM(10);
IAM(999);
IAM($FFFF);
end
;
TIntAMという無名メソッドの型を宣言し、IAMという変数を宣言しています。IAMには手続きの実体を代入しています。手続きの実体は代入時にそのまま記述してしまっているのが従来のDelphiコードにはみられない特徴です。
変数IAMに手続きの実体を代入した後は、実行することが可能です。IAM(10)、IAM(999)、IAM($FFFF)が実行している部分です。それぞれ、10、999、65,535という数字がメッセージボックスとして表示されます。なお、無名メソッドの引数には定数だけではなく変数も渡すことが可能です。しかも、無名メソッドが定義されているスコープ内で宣言されているローカル変数も引数として渡すことが可能です。
Synchronizeと無名メソッド
Delphi 2009のスレッドは、Synchronizeメソッドの引数に無名メソッドを指定することが可能です。無名メソッドを利用することでSynchronizeの問題点を解決することが出来ます。無名メソッドを利用した具体的なコードを以下に示します。
スレッドオブジェクトのユニット:NewThread.pas
unit
NewThread;
interface
uses
Classes, SysUtils;
type
TNewThread = class
(TThread)
private
protected
procedure
Execute; override
;
end
;
implementation
uses
Unit1;
procedure
TNewThread.Execute;
var
i: Integer;
A,B: Integer;
Sync: TThreadProcedure;
begin
Sync := procedure
begin
Form1.Memo1.Lines.Add('NT : '
+ FormatFloat('#,##0'
,A-B));
end
;
for
i := 0 to
99 do
begin
A := Random(10000);
B := Random(10000);
Synchronize(Sync);
end
;
end
;
end
.
メインフォームのユニット:Unit1.pas(一部)
uses
......, NewThread;
type
TForm1 = class
(TForm)
public
NT: TNewThread;
procedure
EndNT(Sender: TObject);
end
;
procedure
TForm1.Button2Click(Sender: TObject);
begin
Memo1.Clear;
Button2.Enabled := False;
NT := TNewThread.Create(False);
NT.FreeOnTerminate := True;
NT.OnTerminate := EndNT;
end
;
procedure
TForm1.EndNT(Sender: TObject);
begin
Button2.Enabled := True;
end
;
procedure
TForm1.FormCreate(Sender: TObject);
begin
Randomize;
end
;
先ほどのOldThread.pasに記述されていたコードとは何点か違いがあることが分かると思います。まず、Synchronizeメソッドの引数に無名メソッド変数を指定しているため、Synchronizeメソッドの引数に渡していた手続きが無くなっています。また、媒介用のプライベートフィールドも存在しません。
コードの解説をすると、Executeメソッドではまず、TThreadProcedure型の変数Syncに無名メソッドの手続きの実体を渡しています。TThreadProcedure型はClassesユニットで宣言されています(実体はTThreadProcedure = reference to procedureとなっているだけです。)これが、Synchronizeメソッドの引数に渡されるため、OldThread.pasにあったSyncMethodと同じ役割を果たします。注目すべきは、SyncMethodではExecuteメソッドのローカル変数であるAとBが直接利用できなかったために、媒介としてクラスにInteger型のフィールドであるFResultIntを用意する必要がありましたが、無名メソッドを用いるとExecuteメソッドのローカル変数を無名メソッド内で利用できるため、媒介用のフィールドが必要なくなるのです。後はAとBにランダムで整数値を代入し、Synchronizeメソッドに先ほどの無名メソッド変数を代入しています。このコードを実行すると以下のようになります。
実行結果

ちなみに無名メソッド変数をいちいち用意しなくても、以下のようにSynchronizeメソッドの引数部分に直接無名メソッドを記述してもOKです。
procedure
TNewThread.Execute;
var
i: Integer;
A,B: Integer;
begin
for
i := 0 to
99 do
begin
A := Random(10000);
B := Random(10000);
Synchronize(procedure
begin
Form1.Memo1.Lines.Add('NT : '
+ FormatFloat('#,##0'
,A-B));
end
);
end
;
end
;
上記コードでは無名メソッド変数の宣言を省くことが出来るという利点がありますが、若干読みづらいかもしれません。注意点として、無名メソッドのendのあとにセミコロン(;)を付けてはいけません。
当然ですがDelphi 2009でも従来のコードは動作します。しかし、無名メソッドを利用することでも、従来のコード同等の結果を得ることが出来ました。しかも、余計なフィールド利用を省くことまでも出来ています。無名メソッドは速度的に若干のペナルティがあり、少々特殊な構文なので読みにくいという欠点がありますが、利用価値は十分にあると思うので、是非利用してみてください。
Connect with Us