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 显示甘特图,描述一个或多个工具机的状态、警报或加工订单。在主管 PC 上,服务器(配备 TIdTcpServer 和一些 DB 组件)正在 LAN 上监听我的应用程序。

主窗体客户端允许最终用户选择一系列日期(期间)和单位(机器)来查询服务器。之后,用户按下一个按钮(有 3 个功能):创建一个新表单(和 Datamodule)来显示结果。

收集数据的工作是由一个线程完成的,因为:

1)这可能是一项很长的工作,因此它可能会冻结 GUI;

2) 用户可以启动多个表单来查看各种结果。

我有一个基本的 Datamodule(带有一个 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,我还有两个单元:

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

就像 Lieven 所写的,这取决于。

如果数据模块上有数据库组件,则必须知道它们是否是线程安全的,或者使它们成为线程安全的。
一些数据库组件要求每个线程有一个单独的会话对象。

于 2010-03-02T09:37:40.853 回答
0

简短的回答:是的

长答案:Windows 的问题是所有的 GUI 活动都应该在一个线程中完成。(嗯,上面的陈述可以扩展、修改、增强等,但对于我们的讨论来说已经足够了)。因此,如果您确定在您的 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

There is a problem where you work with datamodule in Thread: If you terminate your thread in OnDestroy event of form and are waiting for it (WaitFor) - you'll have a deadlock. Main UI thread set lock

procedure TCustomForm.BeforeDestruction;
begin
  GlobalNameSpace.BeginWrite;

and your thread will wait infinitely in it's datamodule destructor with the same

destructor TDataModule.Destroy;
begin
  if not (csDestroying in ComponentState) then GlobalNameSpace.BeginWrite;

So, if you want to wait for your threads when close MainForm, do it in OnClose event or in Project's main file

Or you can destroy it in Synchronize

于 2020-10-06T18:32:33.767 回答