Multithreading 在TThread.Execute中调用TDataModule方法

Multithreading 在TThread.Execute中调用TDataModule方法,multithreading,delphi,datamodule,Multithreading,Delphi,Datamodule,一般来说,在TThread.Execute过程中是否可能 调用TDataModule方法,其中不涉及任何可视活动 谢谢大家,马西莫。简短回答:是的 详细回答:Windows的问题是所有GUI活动都应该在一个线程中完成。(好吧,上述声明可以扩展、修改、增强等,但我们的讨论就足够了)。因此,如果您确定在TDataModule方法中没有涉及任何“GUI内容”(注意,这甚至可能是一个ShowMessage调用),那么请继续 更新:当然,有一些技术可以从辅助线程更新GUI,但这意味着某种准备(消息传递、S

一般来说,在TThread.Execute过程中是否可能 调用TDataModule方法,其中不涉及任何可视活动


谢谢大家,马西莫。

简短回答:是的

详细回答:Windows的问题是所有GUI活动都应该在一个线程中完成。(好吧,上述声明可以扩展、修改、增强等,但我们的讨论就足够了)。因此,如果您确定在TDataModule方法中没有涉及任何“GUI内容”(注意,这甚至可能是一个
ShowMessage
调用),那么请继续


更新:当然,有一些技术可以从辅助线程更新GUI,但这意味着某种准备(消息传递、
Synchronize
等)。这不是很难,只是你不能从另一个线程“盲目”调用一个改变GUI的方法。

当被问到任何问题时,使用我们业界最喜欢的答案:视情况而定。

如果您的datamodule上有一个完全自包含的方法(即可以是一个静态方法),那么您不应该有任何问题

示例

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;

另一方面,如果该方法使用任何全局状态,则从多个线程调用它时可能会遇到问题

示例

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,使用任何全局变量。。。都是全局状态,并且不是线程安全的。

最简单的方法是使用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或其他线程安全的等效方式间接地向主线程发送需要完成某些操作的信号。
    • 想想当你关机时会发生什么。也许您可以检查您的数据模块是否存在,因为在调用其方法之前,它可能已经消失了

就像利文写的那样,这要看情况而定

如果datamodule上有数据库组件,则必须知道这些组件是线程安全的还是线程安全的。

有些数据库组件要求每个线程有一个单独的会话对象。

是的,我的问题很模糊

unit dtmPDoxClientU;

  TdtmPDoxClient = class(TDataModule)
    IdTCPClient: TIdTCPClient;
    ...
    function GetData(...): boolean; 
    ...
  end;
我的程序是一个图形统计应用程序,它必须通过TChart显示甘特图,描述一台或多台机床的状态、报警或加工顺序。 在supervisor PC上安装一台服务器(配备一台TIdTcpServer和一些DB组件) 正在局域网上收听我的应用程序

主表单客户端允许最终用户选择一系列日期(期间)和日期 用于查询服务器的单元(机器)。之后,用户按下一个按钮(有 3个功能):创建一个新表单(和数据模块)以显示结果

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

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

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

我有一个基本的数据模块(带有一个TIdTcpClient,它有几个收集数据的函数), 一个基本表单(从未实例化,具有所有数据表单共有的许多特征,以及工作线程的定义)


此外,使用VFI,我还有两个单元:

unit dtmPDoxClientDataOIU;

  TdtmPDoxClientDataOI = class(TdtmPDoxClient)
    cdsClient_IS: TClientDataSet;
    ...
    dsr_I: TDataSource;
    ...
  private
  public
  end;

单元frmpdoxclientdata;
TfrmPDoxClientDataOI=类(TfrmChartBaseForm)
查托伊:特哈特;
...
过程表单创建(发送方:ToObject);
公众的
{公开声明}
dtmPDoxClientDataOI:TdtmPDoxClientDataOI;
过程ListenThreadEvents(var消息:TMessage);推翻
过程ExecuteInThread(AThread:TThreadClient);推翻
结束;
过程TfrmPDoxClientDataOI.FormCreate(发送方:TObject);
开始
继承;
dtmPDoxClientDataOI:=TdtmPDoxClientDataOI.Create(self);
线程任务:=1;
线程恢复;
结束//入事件
过程TfrmPDoxClientDataOI.ListenThreadEvents(var消息:TMessage);
开始
如果(Message.WParam=1),则开始
的case Message.LParam
//GUI任务,使用已编译且未重复使用的ClientDataset
结束//案例
结束//如果
结束//ListenThreadEvents
过程TfrmPDoxClientDataOI.ExecuteInThread(AThread:TThreadClient);
开始
而不是AThread.Terminated和(AThread.Task 0)确实开始
案件审理任务
1:开始
如果dtmPDoxClientDataOI.GetData(…),则
如果未读取。终止,则开始
PostMessage(Handle,WM_THREADCOMM,1,1);
AThread.Task:=2;
结束//如果
其他的
AThread.Task:=0;
结束//1.
... 等
结束//案例
结束//虽然
结束//执行者
因此,当最终用户按下按钮时,一个新表单及其
procedure TCustomForm.BeforeDestruction;
begin
  GlobalNameSpace.BeginWrite;
destructor TDataModule.Destroy;
begin
  if not (csDestroying in ComponentState) then GlobalNameSpace.BeginWrite;