3

一般に、TThread.Execute プロシージャで、ビジュアル アクティビティを含まない TDataModule メソッドを呼び出すことはできますか?

みんなありがとう、マッシモ。

4

6 に答える 6

2

最も簡単な方法は、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マルチスレッドプログラミングのスタータールールを簡単に説明すると、次のようになります。

  • 競合状態を引き起こす可能性のあることは何もしないでください。
  • クラス(データモジュール)または任意のグローバルのデータフィールドにアクセスするときは常に、クリティカルセクション、ミューテックスなどの同期プリミティブを使用して、競合状態などの同時実行の問題から保護することを忘れないでください。これらを不適切に使用すると、問題のリストにデッドロックが追加されます。したがって、これは混乱させるのに適した場所ではありません。
  • 何らかの方法でVCLコンポーネントまたはオブジェクトにアクセスする必要がある場合は、PostMessage、TThread.Synchronize、または何かを行う必要があることをメインスレッドに通知する他のスレッドセーフな同等の方法を介して間接的にアクセスします。
    • シャットダウンするとどうなるか考えてみてください。メソッドを呼び出す前に、データモジュールがなくなっている可能性があるため、データモジュールが存在するかどうかを確認できるかもしれません。
于 2010-03-01T20:23:36.890 に答える
0

はい、私の質問は非常に漠然としています。

私のプログラムはグラフィカルな統計アプリです。ガント チャートを 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 がフォームに送信され、グラフが更新されます。

于 2010-03-02T14:06:53.913 に答える
0

短い答え:はい

長い回答: Windows の問題は、すべての GUI アクティビティを 1 つのスレッドで実行する必要があることです。(まあ、上記の声明は拡張、修正、強化などすることができますが、私たちの議論には十分です). そのため、TDataModule メソッドに「GUI に関するもの」が含まれていないことが確実な場合 (これはShowMessage呼び出しである可能性があることに注意してください)、先に進みます。

更新: もちろん、セカンダリ スレッドから GUI を更新する手法はありますが、これは何らかの準備 (メッセージの受け渡しSynchronizeなど) を意味します。GUIを変更するメソッドを別のスレッドから「盲目的に」呼び出すことはできません。

于 2010-03-01T16:03:54.800 に答える
0

何かを尋ねられたときの私たちの業界のお気に入りの答えを使用するには、次のようにします。

データモジュールに完全に自己完結型のメソッド (つまり、静的メソッドである可能性がある) がある場合、問題はないはずです。

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 の更新、グローバル変数の使用などはすべてグローバルな状態であり、スレッド セーフではありません。

于 2010-03-01T16:11:34.270 に答える
0

リーベンが書いているように、それは場合によります。

データモジュールにデータベース コンポーネントがある場合は、それらがスレッド セーフかどうか、またはそれらをスレッド セーフにするかどうかを確認する必要があります。
一部のデータベース コンポーネントでは、スレッドごとに個別のセッション オブジェクトが必要です。

于 2010-03-02T09:37:40.853 に答える