一般に、TThread.Execute プロシージャで、ビジュアル アクティビティを含まない TDataModule メソッドを呼び出すことはできますか?
みんなありがとう、マッシモ。
一般に、TThread.Execute プロシージャで、ビジュアル アクティビティを含まない TDataModule メソッドを呼び出すことはできますか?
みんなありがとう、マッシモ。
最も簡単な方法は、TThread.Synchronizeを使用してデータモジュールのメソッドを呼び出すことです。
ただし、それを望まない場合は、視覚的なアクティビティが含まれていない場合でも、保護するためにクリティカルセクションを追加する必要があるかどうかを判断する必要があります。
標準またはサードパーティのVCLコンポーネントへのアクセスは、ビジュアル(TButton)か非ビジュアル(データセット)かに関係なく、安全ではないと見なす必要があります。ローカルデータオブジェクト(プライベートフィールドやグローバル変数など)へのアクセスも、クリティカルセクションで保護する必要があります。
バックグラウンドスレッドからデータモジュールへの直接呼び出しは次のとおりです。
if Assigned(MyDataModule) then MyDataModule.DoSomething(a,b,c);
これがデータモジュールのコードです。これは、現在FListにアクセスしている唯一のスレッドであることを確認するコードのサンプルビットを示しています。
/// DoSomething: Note this method must be thread-safe!
procedure TMyDataModule.DoSomething(a:TMyObject1;b:TMyObject2;c:TMyObject3);
begin
FCriticalSection.Enter;
try
if not FList.Contains(a) then
FList.Add(a);
...
finally
FCriticalSection.Leave;
end;
end;
/// elsewhere in the same data module, wherever anybody modifies or checks the state
/// (content) of FList, wrap the method with a critical section like this:
function TMyDataModule.HasItem(a:TMyObject1):Boolean;
begin
FCriticalSection.Enter;
try
result := FList.Contains(a);
finally
FCriticalSection.Leave;
end;
end;
Delphiマルチスレッドプログラミングのスタータールールを簡単に説明すると、次のようになります。
はい、私の質問は非常に漠然としています。
私のプログラムはグラフィカルな統計アプリです。ガント チャートを TChart を使用して表示し、1 つまたは複数のツール マシンの状態、アラーム、または加工順序を説明する必要があります。スーパーバイザー PC では、サーバー (TIdTcpServer といくつかの DB コンポーネントを搭載) が LAN 上のアプリをリッスンしています。
メイン フォーム クライアントを使用すると、最終ユーザーは日付の範囲 (期間) と単位 (マシン) を選択してサーバーに問い合わせることができます。その後、ユーザーはボタンを押します (3 つの機能があります)。新しいフォーム (およびデータモジュール) が作成され、結果が表示されます。
次の理由により、データ収集の作業はスレッドによって完了します。
1) 長い作業になる可能性があるため、GUI がフリーズする可能性があります。
2) ユーザーは複数のフォームを起動して、さまざまな結果を表示できます。
基本的なデータモジュール (データを収集するためのいくつかの関数を備えた TIdTcpClient を使用)、基本的なフォーム (インスタンス化されず、すべてのデータフォームに共通する多くの特性、およびワーカースレッドの定義) があります。
unit dtmPDoxClientU;
TdtmPDoxClient = class(TDataModule)
IdTCPClient: TIdTCPClient;
...
function GetData(...): boolean;
...
end;
unit frmChartBaseFormU;
TfrmChartBaseForm = class(TForm)
...
TheThread: TThreadClient;
procedure WMThreadComm(var Message: TMessage); message WM_THREADCOMM;
procedure ListenThreadEvents(var Message: TMessage); virtual;
procedure ExecuteInThread(AThread: TThreadClient); virtual;
end;
TThreadClient = class(TThread)
private
public
Task: integer;
Module: TfrmChartBaseForm;
procedure Execute; override;
property Terminated;
end;
procedure TfrmChartBaseForm.FormCreate(Sender: TObject);
...
TheThread := TThreadClient.Create(true);
with TheThread do begin
Module := self;
FreeOnTerminate := true;
end;//with
end;//FormCreate
procedure TfrmChartBaseForm.WMThreadComm(var Message: TMessage);
begin
ListenThreadEvents(Message);
end;//WMThreadComm
procedure TfrmChartBaseForm.ListenThreadEvents(var Message: TMessage);
begin
// do override in derived classes
end;//ListenThreadEvents
procedure TfrmChartBaseForm.ExecuteInThread(AThread: TThreadClient);
begin
// do override in derived classes
end;//ExecuteInThread
procedure TThreadClient.Execute;
begin
with Module do begin
ExecuteInThread(self);
end;//with
end;//Execute
さらに、VFI を使用して、次の 2 つのユニットもあります。
unit dtmPDoxClientDataOIU;
TdtmPDoxClientDataOI = class(TdtmPDoxClient)
cdsClient_IS: TClientDataSet;
...
dsr_I: TDataSource;
...
private
public
end;
unit frmPDoxClientDataOIU;
TfrmPDoxClientDataOI = class(TfrmChartBaseForm)
ChartOI: TChart;
...
procedure FormCreate(Sender: TObject);
public
{ Public declarations }
dtmPDoxClientDataOI: TdtmPDoxClientDataOI;
procedure ListenThreadEvents(var Message: TMessage); override;
procedure ExecuteInThread(AThread: TThreadClient); override;
end;
procedure TfrmPDoxClientDataOI.FormCreate(Sender: TObject);
begin
inherited;
dtmPDoxClientDataOI := TdtmPDoxClientDataOI.Create(self);
TheThread.Task := 1;
TheThread.Resume;
end;//FormCreate
procedure TfrmPDoxClientDataOI.ListenThreadEvents(var Message: TMessage);
begin
if (Message.WParam = 1) then begin
case Message.LParam of
//GUI tasks, using ClientDataset already compiled and not re-used
end;//case
end;//if
end;//ListenThreadEvents
procedure TfrmPDoxClientDataOI.ExecuteInThread(AThread: TThreadClient);
begin
while not AThread.Terminated and (AThread.Task <> 0) do begin
case AThread.Task of
1: begin
if dtmPDoxClientDataOI.GetData(...) then
if not AThread.Terminated then begin
PostMessage(Handle,WM_THREADCOMM,1,1);
AThread.Task := 2;
end //if
else
AThread.Task := 0;
end;//1
... etc...
end;//case
end;//while
end;//ExecuteInThread
そのため、最終ユーザーがボタンを押すと、新しいフォームと独自のデータ モジュールおよびスレッドが作成されます。スレッドは、ExecuteInThread 関数によって独自のデータ モジュールを使用します。データの準備ができると、PostMessage がフォームに送信され、グラフが更新されます。
短い答え:はい
長い回答: Windows の問題は、すべての GUI アクティビティを 1 つのスレッドで実行する必要があることです。(まあ、上記の声明は拡張、修正、強化などすることができますが、私たちの議論には十分です). そのため、TDataModule メソッドに「GUI に関するもの」が含まれていないことが確実な場合 (これはShowMessage
呼び出しである可能性があることに注意してください)、先に進みます。
更新: もちろん、セカンダリ スレッドから GUI を更新する手法はありますが、これは何らかの準備 (メッセージの受け渡しSynchronize
など) を意味します。GUIを変更するメソッドを別のスレッドから「盲目的に」呼び出すことはできません。
何かを尋ねられたときの私たちの業界のお気に入りの答えを使用するには、次のようにします。
データモジュールに完全に自己完結型のメソッド (つまり、静的メソッドである可能性がある) がある場合、問題はないはずです。
例
TMyDataModule = class(TDataModule)
public
function AddOne(const Value: Integer): Integer;
end;
function TMyDataModule.AddOne(const Value: Integer): Integer;
begin
Result := Value + 1;
end;
一方、メソッドがグローバル状態を使用する場合、複数のスレッドから呼び出すときに問題が発生する可能性があります。
例
TMyDataModule = class(TDataModule)
private
FNumber: Integer
public
function AddOne(const Value: Integer): Integer;
end;
function TMyDataModule.AddOne(const Value: Integer): Integer;
begin
FNumber := Value
//***** A context switch here will mess up the result of (at least) one thread.
Result := FNumber + 1;
end;
グローバルな状態は非常に広く解釈されるべきです。TQuery、TTable、GUI の更新、グローバル変数の使用などはすべてグローバルな状態であり、スレッド セーフではありません。
リーベンが書いているように、それは場合によります。
データモジュールにデータベース コンポーネントがある場合は、それらがスレッド セーフかどうか、またはそれらをスレッド セーフにするかどうかを確認する必要があります。
一部のデータベース コンポーネントでは、スレッドごとに個別のセッション オブジェクトが必要です。