在 TThread.Execute 中调用 TDataModule 方法

发布于 2024-08-23 00:17:07 字数 84 浏览 4 评论 0原文

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

谢谢大家,马西莫。

In general, is it possible in a TThread.Execute procedure
to call a TDataModule method, in which there is no visual activity involved?

Thanks to all, Massimo.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

写给空气的情书 2024-08-30 00:17:07

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

The easiest way to go is to use TThread.Synchronize to invoke a method in your data module.

However, if you do not wish to do that, even when no visual activity is involved, you should determine whether or not you need to add a critical section to protect you.

Any access to any standard or third-party VCL component, whether it is visual (TButton) or non-visual (datasets) should be considered UNSAFE. Any access to a local data object (like a private field or global variable) must also be protected by critical sections.

Here's a direct call from a from background thread to your data module:

    if Assigned(MyDataModule) then MyDataModule.DoSomething(a,b,c);

Here's the code in your data module, which I am showing you a sample bit of code that makes sure that we are the only thread touching FList right now:

/// 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;

Some starter rules for Delphi multi-threaded programming, in a nutshell are:

  • Don't do anything that could create a Race Condition.
  • Don't forget to use synchronization primitives like Critical Sections, Mutexes, etc, to protect against concurrency issues including Race Conditions, whenever you are accessing any data fields in your class (data module) or ANY globals. If you use these improperly you add deadlocks to your list of problems. So this is not a good place to mess up.
  • If you must access a VCL component or object in any way, do so indirectly via PostMessage, TThread.Synchronize, or some other thread-safe equivalent way of signaling the main thread that you need something done.
    • Think about what happens when you're shutting down. Maybe you could check if your data module even exists, since it might have gone away, before you invoke its methods.
熊抱啵儿 2024-08-30 00:17:07

简短回答:

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

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

Short answer: yes

Long answer: The problem with Windows is that all the GUI activity should be done in a single thread. (Well, the above statement can be expanded, amended, enhanced etc. but for our discussion is enough). So, if you are sure that in your TDataModule method there isn't any 'GUI thing' involved (beware, this can be even a ShowMessage call) then go ahead.

UPDATE: Of course, there are techniques to update your GUI from a secondary thread, but this implies some sort of preparation (message passing, Synchronize etc.). Isn't something very hard, just that you cannot 'blindly' call from another thread a method who changes the GUI.

〆一缕阳光ご 2024-08-30 00:17:07

当被问及任何问题时,要使用我们行业最喜欢的答案:这取决于。

如果您的数据模块上有一个完全独立的方法(即可以是静态方法),那么您应该不会有任何问题。

示例

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

To use our industries favorite answer when asked anything: It depends.

If you have a method on your datamodule that is completely self contained (ie could be a static method), you shouldn't have any problem.

Example

TMyDataModule = class(TDataModule)
public
  function AddOne(const Value: Integer): Integer;
end;

function TMyDataModule.AddOne(const Value: Integer): Integer;
begin
  Result := Value + 1;
end;

If on the other hand, the method uses any global state, you might get into trouble when calling it from multiple threads.

Example

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;

Global state should be interpreted very wide. A TQuery, a TTable, refreshing the GUI, using any global variable, ... is all global state and isn't thread safe.

月下客 2024-08-30 00:17:07

是的,我的问题很模糊。

我的程序是一个图形统计应用程序,它必须通过TChart显示甘特图,描述一台或多台机床的状态、警报或加工订单。
在主管 PC 上的服务器(配备 TIdTcpServer 和一些 DB 组件)
正在局域网上监听我的应用程序。

主窗体客户端允许最终用户选择日期范围(期间)和
查询服务器的单位(机器)。之后,用户按下一个按钮(有
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,我还有两个单元:

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,该表单会更新
图表。

Yes, my question is very vague.

My program is a graphical statistics app, it has to display Gantt chart, by means of TChart, describing the states, alarms or machined orders of one or more Tool Machine.
On the supervisor PC a server (equipped with a TIdTcpServer and some DB components)
is listening to my app on the LAN.

The main-form client allows the final user to choice a range of dates (period) and
the units (machines) to query the server. After that, the user press a button (there are
3 functionalities): a new form (and Datamodule) is created to display the results.

The work of collecting data is completed by a thread because:

1) it can be a long job so it could freeze the GUI;

2) the user can launch more than one form to see various results.

I have a basic Datamodule (with a TIdTcpClient with several function to collect the data),
a basic form (never instantiated, with a lot of characteristics common to all data form, and the definition of the worker thread).

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

Furthermore, using VFI, I also have two units:

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

So, when the final user presses the button, a new form and its own datamodule and
thread are created; the thread uses its own datamodule by means of ExecuteInThread
function. When data are ready, a PostMessage is sent to the form, which updates
the chart.

梦里人 2024-08-30 00:17:07

正如利文所写,这取决于情况。

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

Like Lieven writes, it depends.

If you have database components on the datamodule, you have to know if the are thread safe, or to make them threadsafe.
Some database components require a seperate session object per thread.

腻橙味 2024-08-30 00:17:07

在线程中使用数据模块时存在问题:
如果您在窗体的 OnDestroy 事件中终止线程并等待它 (WaitFor) - 您将遇到死锁。
主 UI 线程设置锁定

procedure TCustomForm.BeforeDestruction;
begin
  GlobalNameSpace.BeginWrite;

,您的线程将在其数据模块析构函数中无限等待,因此

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

,如果您想在关闭 MainForm 时等待线程,请在 OnClose 事件或项目的主文件中执行操作,

或者您可以在 Synchronize 中销毁它

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文