私は最近、INDY10 TCP / IPサーバーの基本機能に関する1つのデモオープンソースプロジェクトを試してみましたが、INDYの内部マルチタスク実装とVCLコンポーネントとの相互作用の問題に遭遇しました。SOにはこのテーマに関するさまざまなトピックがあるため、単純なクライアントサーバーアプリケーションを作成し、提案されたソリューションとアプローチのいくつか、少なくとも正しく理解したものをテストすることにしました。以下に、SOで以前に提案されたアプローチを要約して確認し、可能であれば、このテーマに関する専門家の意見に耳を傾けたいと思います。
問題:indy10ベースのクライアント/サーバーアプリケーション内でスレッドセーフに使用できるようにVCLをカプセル化します。
開発環境の説明:Delphiバージョン:Delphi®XE2バージョン16.0INDYバージョン10.5.8.0OS Windows 7(32ビット)
記事で述べたように([VCLスレッドセーフですか?])(リンクを投稿するのに十分な評判がありません)マルチスレッド(マルチタスク)内で任意の種類のVCLコンポーネントを使用する場合は、特別な注意が必要です。応用。VCLはスレッドセーフではありませんが、スレッドセーフな方法で使用できます。方法と理由は通常、手元のアプリケーションによって異なりますが、少し一般化して、この問題に対するある種の一般的なアプローチを提案することができます。まず、INDY10の場合のように、VCLをデッドロックやデータの相互依存関係にさらすために、コードを明示的に並列化する必要はありません。つまり、複数のスレッドを作成して実行する必要はありません。
すべてのsclient-serverアプリケーションで、サーバーは複数の要求を同時に処理できる必要があるため、当然、INDY10はこの機能を内部的に実装します。これは、INDY10のクラスのセットが、プログラムのスレッドの作成、実行、および破棄の手順を内部で管理する責任があることを意味します。
コードがINDY10の内部動作にさらされ、スレッドの競合が発生する可能性がある最も明白な場所は、IdTCPServerExecute(TIdTCPServer onExecuteイベント)メソッドです。
当然、INDY10はスレッドセーフなプログラムフローを保証するクラス(ラッパー)を提供しますが、それらのアプリケーションと使用法について十分な説明を得ることができなかったため、カスタムメイドのアプローチを好みます。
以下に、この問題の処理を試みる(そしておそらく成功する)メソッド(提案された手法は、SOで見つけた以前のコメントに基づいています。Indy10のTIdThreadSafeクラスの使用方法)を要約します。
以下で私が取り組む質問は、特定のクラス「MyClass」をスレッドセーフにする方法です。
主なアイデアは、「MyClass」をカプセル化し、先入れ先出しの原則でそれにアクセスしようとするスレッドをキューに入れる一種のラッパークラスを作成することです。同期に使用される基になるオブジェクトは、[Windowsのクリティカルセクションオブジェクト]です。
クライアント/サーバーアプリケーションのコンテキストでは、「MyClass」にはサーバーのすべてのスレッドセーフでない機能が含まれるため、これらのプロシージャと関数が複数の作業スレッドによって同時に実行されないようにします。これは当然、コードの並列処理が失われることを意味しますが、アプローチは単純であり、そう思われるため、場合によっては、これは有用なアプローチである可能性があります。
ラッパークラスの実装:
constructor TThreadSafeObject<T>.Create(originalObject: T);
begin
tsObject := originalObject; // pass it already instantiated instance of MyClass
tsCriticalSection:= TCriticalSection.Create; // Critical section Object
end;
destructor TThreadSafeObject<T>.Destroy();
begin
FreeAndNil(tsObject);
FreeAndNil(tsCriticalSection);
inherited Destroy;
end;
function TThreadSafeObject<T>.Lock(): T;
begin
tsCriticalSection.Enter;
result:=tsObject;
end;
procedure TThreadSafeObject<T>.Unlock();
begin
tsCriticalSection.Leave;
end;
procedure TThreadSafeObject<T>.FreeOwnership();
begin
FreeAndNil(tsObject);
FreeAndNil(tsCriticalSection);
end;
MyClassの定義:
MyClass = class
public
procedure drawRandomBitmap(abitmap: TBitmap); //Draw Random Lines on TCanvas
function decToBin(i: LongInt): String; //convert decimal number to Bin.
procedure addLineToMemo(aLine: String; MemoFld: TMemo); // output message to TMemo
function randomColor(): TColor;
end;
使用法:
スレッドは順番に実行され、クリティカルセクションの現在の所有権を持つスレッド(tsCriticalSection.Enter;およびtsCriticalSection.Leave;)が終了するのを待つため、その所有権リレーを管理する場合は、1つの一意のインスタンスが必要です。TThreadSafeObject(シングルトンパターンの使用を検討できます)。したがって、以下を含めます。
tsMyclass:= TThreadSafeObject<MyClass>.Create(MyClass.Create);
Form.Createと
tsMyclass.Destroy;
Form.Closeで; ここで、tsMyclassはMyClass型のグローバル変数です。
使用法:
MyClassの使用法については、次のことを試してください。
with tsMyclass.Lock do
try
addLineToMemo('MemoLine1', Memo1);
addLineToMemo('MemoLine2', Memo1);
addLineToMemo('MemoLine3', Memo1);
finally
// release ownership
tsMyclass.unlock;
end;
、ここで、Memo1はフォーム上のTMemoコンポーネントのインスタンスです。
これにより、tsMyClassがロックされているときに発生するすべてのことが、一度に1つのスレッドによってのみ実行されるようにする必要があります。ただし、このアプローチの明らかな欠点は、tsMyclassのインスタンスが1つしかないため、1つのスレッドがたとえばCanvasで描画しようとしていて、別のスレッドがメモに書き込んでいる場合でも、最初のスレッドは待機する必要があることです。 2番目に終了し、その場合にのみ、その仕事を実行できるようになります。
ここでの私の質問は次のとおりです。
- 上記の提案された方法は正しいですか?私はまだ競合状態に陥っていませんか、それともデータの競合が発生する可能性のあるコードに「抜け穴」がありますか?
- 一般に、アプリケーションのスレッドの安全性をテストするにはどうすればよいですか?
上記のアプローチは決して私自身のやり方ではないことを強調したいと思います。これは基本的に2で見つかった解決策の要約です。それにもかかわらず、私は、トピックに関するある種の閉鎖または提案された解決策のある種の有効性の証明を得るために、再度投稿することにしました。その上、彼らが言うように、繰り返しはすべての知識の母です。