通常,是否可以在TThread.Execute过程中使用 调用TDataModule方法,其中没有涉及视觉活动?
感谢所有人,Massimo。
答案 0 :(得分:2)
最简单的方法是使用TThread.Synchronize来调用数据模块中的方法。
但是,如果您不希望这样做,即使不涉及视觉活动,您也应该确定是否需要添加critical section来保护您。
对任何标准或第三方VCL组件的任何访问,无论是可视(TButton)还是非可视(数据集)都应被视为UNSAFE。任何对本地数据对象(如私有字段或全局变量)的访问也必须受到关键部分的保护。
这是从后台线程到数据模块的直接调用:
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多线程编程的一些启动规则,简而言之是:
答案 1 :(得分:0)
简短回答:是
答案很长:Windows的问题是所有的GUI活动都应该在一个线程中完成。 (好吧,上面的陈述可以扩展,修改,增强等等,但我们的讨论就足够了)。所以,如果您确定在您的TDataModule方法中没有涉及任何“GUI事物”(请注意,这甚至可以是ShowMessage
调用),那么请继续。
更新:当然,有一些技术可以从辅助线程更新您的GUI,但这意味着某种准备(消息传递,Synchronize
等)。不是很难,只是你不能“盲目地”从另一个线程调用一个改变GUI的方法。
答案 2 :(得分: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,使用任何全局变量,......都是全局状态,并且不是线程安全的。
答案 3 :(得分:0)
像列文所写,这取决于。
如果数据模块上有数据库组件,则必须知道线程是否安全,或者是否使线程安全。
某些数据库组件需要每个线程一个单独的会话对象。
答案 4 :(得分:0)
是的,我的问题很模糊。
我的程序是一个图形统计应用程序,它必须通过TChart显示甘特图,描述一个或多个工具机的状态,报警或加工订单。 在主管PC上一台服务器(配备TIdTcpServer和一些数据库组件) 正在局域网上收听我的应用程序。
主表单客户端允许最终用户选择一系列日期(句点)和 用于查询服务器的单元(机器)。之后,用户按下按钮(有 3个功能):创建一个新表单(和Datamodule)来显示结果。
收集数据的工作由一个线程完成,因为:
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将发送到表单,并更新 图表。
答案 5 :(得分:0)
在线程中使用数据模块时出现问题: 如果您以表单的OnDestroy事件终止线程并等待它(WaitFor)-您将陷入僵局。 主UI线程集锁定
-----BEGIN PRIVATE KEY-----
{{private key}}
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
{{certificate}}
-----END CERTIFICATE-----
,您的线程将在相同的数据模块析构函数中无限期地等待
procedure TCustomForm.BeforeDestruction;
begin
GlobalNameSpace.BeginWrite;
因此,如果要在关闭MainForm时等待线程,请在OnClose事件或Project的主文件中执行
或者您可以在同步中销毁它