Can I do some initializations before `Inherited Create`? - multithreading

I want to create and start a thread, all with one command line TClientCopyThread.Create(...). For this, I must create the thread with Suspended = False, so that it can run immediately. I know that when I write a constructor of a new object, first of all I must call the inherited Create so that the instance of the object is created, and then do my initializations. But here, if I call inherited the thread will start without initialized parameters. I try to call inherited last and it seems it's working (I don't receive any access violation), but I don't know for sure if this is a coincidence or not.
TClientCopyThread = class(TThread)
private
OwnGUID: String;
SrcPath, DestPath: String;
Files: TFileNames;
RemoveIt: Boolean;
protected
procedure Execute; override;
public
constructor Create(const GUID, ASrcPath, ADestPath: String;
const FileNames: TFileNames; RemoveSrc: Boolean);
end;
constructor TClientCopyThread.Create(const GUID, ASrcPath, ADestPath: String;
const FileNames: TFileNames; RemoveSrc: Boolean);
var I: Integer;
begin
SrcPath:= Copy(ASrcPath, 1, Length(ASrcPath));
DestPath:= Copy(ADestPath, 1, Length(ADestPath));
SetLength(Files, Length(FileNames));
for I:= 0 to High(Files) do
Files[I]:= Copy(FileNames[I], 1, Length(FileNames[I]));
RemoveIt:= RemoveSrc;
FreeOnTerminate:= True;
inherited Create;
end;

To answer your specific question - YES, you can call inherited Create at any point during a derived constructor. It DOES NOT need to be the first statement (same with inherited Destroy as the last statement in the destructor). Memory for the class object has already been allocated in full before any constructors are called, so it is safe to initialize members of your derived class before calling the inherited constructor. However, when accessing base class members from the derived constructor, you should call the inherited constructor first to initialize them before accessing them.
That being said, your understanding of how the TThread constructor works is just plain wrong. Since Delphi 6 onwards, the base class TThread constructor always creates the underlying OS thread in suspended mode, and then resumes the thread in TThread.AfterConstruction() after all constructors have fully exited, if the TThread object is constructed with CreateSuspended=False. So, your claim that calling inherited Create with CreateSuspended=False will start the thread running immediately has NOT been true since 2001 when Delphi 6 was released. The underlying OS thread will NOT start running until after your TClientCopyThread.Create() constructor has exited. Thus, your Execute() method will never act on uninitialized members, regardless of how you set CreateSuspended.
What you describe (the thread running before members were initialized) was a bug in Delphi 5 and earlier, which was fixed in Delphi 6.

Related

How to protect and access nested objects in multithreaded application

I have an object with nested objects. Simple diagram of object organziation would be:
TMainObj
-TState
-TDescriptor
-List<TSubObj>
--TSubObj_n
---TSubObjState
---TSubObjDesc
---TSubObjData
type TState = class
end;
type TDesc = class
end;
type TSubObjState = class
end;
type TSubObjDesc = class
end;
type TSubObjData = class
end;
type TSubObj = class
FSubObjState: TSubObjState;
FSubObjDesc: TSubObjDesc;
FSubObjData: TSubObjData;
end;
type TListSubObj = class (TList<TSubObj>)
end;
type TMainObj = class
FState: TState;
FDesc: TDesc;
FList: TList<TSubObj>
end;
I have multithreaded app and I have to enable access to objects and their properties (which are not included in this example code). Some threads share same objects some not but still they could share some properties with main thread therfore I need to protect data. I am protecting data with critical sections / mutexes. However I don't know how to organize locking mechanism in this scheme to get the best out of it.
My initial idea was to implement lock/unlock on TMainObj and whenever any thread needs to access any property or subobjects it will lock complete TMainObj and all other threads will need to wait until TMainObj becomes unlocked. For that reason I think this not really good idea. Some threads doesn't need to access TMainObj's properties but only it's sub object such as TState. I assume there is no need to lock whole TMainObj but only TState or am I missing something?
If I need to access properties on TMainObj I would do it:
TMainObj.Lock
try
TMainObj.Name := 'Just name!';
TManiObj.Id := 1;
finally
TMainObj.Unlock;
end;
And every other thread will have to wait to gain an access.
But what when I need to access sub class TDescriptor? I can do it like that:
TMainObj.Lock
try
TMainObj.Descriptor.DataLen := 1024;
TManiObj.Descriptor.Count := 10;
finally
TMainObj.Unlock;
end;
And complete TMainObj will be locked. And all other threads need to wait even if they are not 'interested' in changing TMainObj's properties.
Or that way to lock only sub object descriptor:
Thread1:
TMainObj.Descriptor.Lock
try
TMainObj.Descriptor.DataLen := 1024;
TManiObj.Descriptor.Count := 10;
finally
TMainObj.Descriptor.Unlock;
end;
Meanwhile some other thread can still access TMainObj properties and change them, right?
Thread2:
TMainObj.Lock;
try
TMainObj.Name := 'New name!';
finally
TMainObj.Unlock;
end;
Here is the image which shows how and what each thread is accessing.
One of concerns is a deadlock situation. In next case I would like to show how different threads are accessing different "part" of MainObj.
MainThread:
MainObj.Lock;
try
MainObj.Name = 'Different name!'
MainObj.Id = 2;
finally
MainObj.Unlock;
end;
Meanwhile thread1 is doing this:
MainObj.Descriptor.Lock;
try
MainObj.Descriptor.DataLen = 1024;
MainObj.Descriptor.Count = 1;
finally
MainObj.Descriptor.Unlock;
end;
So both are sharing MainObj but each is changing own part of object. Is that approach of locking appropriate?
I hope I explained my problem as clear as possible. My question is how to protect access to such object structure from different threads? Do I have to protect each subobject with it's own lock/unlock pair methods (and critical section)?
You could use TMonitor for this without adding anything to your objects. In that case your code would look like this:
TMonitor.Enter(MainObj.Descriptor);
try
MainObj.Descriptor.DataLen := 1024;
MainObj.Descriptor.Count := 10;
finally
TMonitor.Exit(MainObj.Descriptor);
end;
Provided all threads (and the main thread) that are trying to access descriptor do the same thing then they will lock waiting for the next thread to finish.
You will need to watch out for deadlocks but from what you say it shouldn't be a problem. A deadlock will occur if you do something like this:
Main Thread
Lock MainObj
Lock MainObj.Descriptor (waits for thread 1)
If thread 1 comes along and does this:
Thread 1
Lock MainObj.Descriptor
Lock MainObj (waits for main thread)

Delphi - accesing non UI units from inside a thread

I have the following situation.
We develop in DelphiXE.
We are putting the majority of our functions in a DATAMODULE.
function1 (database, transaction, paramInteger) : float
for example
function1 take parameters database (TIBDATABASE), the transaction TIBTRANSACTIOn and aditiona parameter integer. and return a float
function GetLastPretAch(DIBase : TIBDatabase; Tran : TIBTransaction; const aID : Integer) : Double;
var workQuery : TIBQuery;
begin
try
workQuery := TIBQuery.Create(Application);
try
workQuery.Close;
workQuery.Database := DIBase;
workQuery.Transaction := Tran;
workQuery.SQL.Clear;
workQuery.SQL.Add('SELECT * FROM GETLASTPRETACH(-1, :AARTNR)');
workQuery.ParamByName('AARTNR').AsInteger := aID;
workQuery.Open;
Result := workQuery.FieldByName('LASTPRET').AsFloat;
except
on e : Exception do begin
raise EMagisterException.Create(TranslateIbError(e));
end;
end;
finally
FreeAndNil(workQuery);
end;
end;
Now I want to use this functions from a thread. is this thread safe?
inside execute procedure like
ID := GetLastPretAch(database, transaction, 1);
is or not thread safe?
The answer to your question is Yes, you can use that function from inside a worker thread's execute procedure. You might want to consider refining your SQL to only SELECT the field LASTPRET instead of SELECT *.
For an extended discussion on what "thread safe" means refer to this SO question
What does threadsafe mean?
Looks like you're using IBX Components which, the last time I looked were most definitely NOT thread-safe. If you switched to a data access layer that was thread-safe, you should be fine with that code. FYI UIB (Unified Interbase components) are thread-safe.

How to access thread and its components?

I create a thread
type
ss_thread = class;
ss_thread = class(TThread)
protected
Fff_id : string;
Fff_cmd : string;
Fff_host : string;
Fff_port : TIdPort;
procedure Execute; override;
public
constructor Create(const ff_id, ff_cmd: string; ff_host: string; ff_port: TIdPort);
end;
constructor ss_thread.Create(const ff_id, ff_cmd: string; ff_host: string; ff_port: TIdPort);
begin
inherited Create(False);
Fff_id := ff_id;
Fff_cmd := ff_cmd;
Fff_host := ff_host;
Fff_port := ff_port;
end;
...
id := 123;
...
nst_ss_thread.Create(id, cmd, host, port);
and doing something on
procedure ss_thread.Execute;
var
ws : TIdTCPClient;
data : TIdBytes;
i : integer;
list : TList;
begin
ws := TIdTCPClient.Create(nil);
ws.Host := Fff_host;
ws.Port := Fff_port;
....
How to access this thread 'ws' variable thru another thread using id:=123 of thread ?
Thanks
It cannot.
You've declared ws as a local variable inside ss_thread.execute, which means it's only visible there. It can't be seen outside ss_thread.execute, even by other parts of ss_thread.
If you want it visible from other places or threads, you need to move it to a more visible scope. For instance, if you want it visible from other places in ss_thread, move it to the interface declaration in private or protected sections, and if you want it visible from outside ss_thread move it to the published or public sections.
You'd better not. Thread objects are exactly made to insulate its variables from other threads.
Otherwise all kind of random non-reproducible errors would appear - http://en.wikipedia.org/wiki/Heisenbug
Parallel programming should have very clear separation and insulation. Because You can never predict the timing of execution and which statement would run earlier and which one later.
Imagine that easy scenario:
ws := TIdTCPClient.Create(nil);
ws.Host := Fff_host;
// at this point another thread gets access to ws variable,
// as You demanded - and changes it, so WS gets another value!
ws.Port := Fff_port;
How would you detect such a bug, if it happens only on client multi-processor computer under heavy load once a month ? In your workstation during debug sessions or simulation it would not be reproduced ever! How would you catch it and fix ?
As a rule of thumb, when doing parallel programming the data should be spleat into "shared immutable" and "private mutable" pieces, and when doing inter-thread communication you should - similar to inter-process communications - make some events/messages queue and pass commands and replies to/from threads, like it is done in Windows GDI or like in MPI
Then you thread would fetch "change ws variable" command from queue - in the proper moment when the change is allowed - and change it from inside. Thus you would assume control and assure that variables are only changed in that point and in that manner, that would not derail the code flow.
I suggest you to read OTL examples to see how inter-thread communication is done in more safe way that direct access to objects. http://otl.17slon.com/tutorials.htm

Delphi (XE2) Indy (10) Multithread Ping

I have a room with 60 computers/devices (40 computers and 20 oscilloscopes Windows CE based) and I would like to know which and every one is alive using ping. First I wrote a standard ping (see here Delphi Indy Ping Error 10040), which is working fine now but takes ages when most computers are offline.
So what I am trying to do is to write a MultiThread Ping but I am quite struggling with it. I have seen only very few examples over the internet and no one was matching my needs, that's why I try to write it myself.
I use XE2 and Indy 10 and the form is only constitued of a memo and a button.
unit Main;
interface
uses
Winapi.Windows, System.SysUtils, System.Classes, Vcl.Forms,
IdIcmpClient, IdGlobal, Vcl.StdCtrls, Vcl.Controls;
type
TMainForm = class(TForm)
Memo1: TMemo;
ButtonStartPing: TButton;
procedure ButtonStartPingClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type
TMyPingThread = class(TThread)
private
fIndex : integer;
fIdIcmpClient: TIdIcmpClient;
procedure doOnPingReply;
protected
procedure Execute; override;
public
constructor Create(index: integer);
end;
var
MainForm: TMainForm;
ThreadCOunt : integer;
implementation
{$R *.dfm}
constructor TMyPingThread.Create(index: integer);
begin
inherited Create(false);
fIndex := index;
fIdIcmpClient := TIdIcmpClient.Create(nil);
fIdIcmpClient.ReceiveTimeout := 200;
fIdIcmpClient.PacketSize := 24;
fIdIcmpClient.Protocol := 1;
fIdIcmpClient.IPVersion := Id_IPv4;
//first computer is at adresse 211
fIdIcmpClient.Host := '128.178.26.'+inttostr(211+index-1);
self.FreeOnTerminate := true;
end;
procedure TMyPingThread.doOnPingReply;
begin
MainForm.Memo1.lines.add(inttostr(findex)+' '+fIdIcmpClient.ReplyStatus.Msg);
dec(ThreadCount);
if ThreadCount = 0 then
MainForm.Memo1.lines.add('--- End ---');
end;
procedure TMyPingThread.Execute;
begin
inherited;
try
fIdIcmpClient.Ping('',findex);
except
end;
while not Terminated do
begin
if fIdIcmpClient.ReplyStatus.SequenceId = findex then Terminate;
end;
Synchronize(doOnPingReply);
fIdIcmpClient.Free;
end;
procedure TMainForm.ButtonStartPingClick(Sender: TObject);
var
i: integer;
myPing : TMyPingThread;
begin
Memo1.Lines.Clear;
ThreadCount := 0;
for i := 1 to 40 do
begin
inc(ThreadCount);
myPing := TMyPingThread.Create(i);
//sleep(10);
end;
end;
end.
My problem is that it "seems" to work when I uncomment the "sleep(10)", and "seems" not to be working without it. This for sure means I am missing a point in the threading I have written.
In other words. When Sleep(10) is in the code. Every time I clicked the button to get to check the connections the result was correct.
Without the sleep(10), it is working "most" of the time but some times the result is wrong giving me a ping echo on offline computers and no ping echo on online computer, as is the ping reply was not assigned to the correct thread.
Any comment or help is welcome.
----- EDIT / IMPORTANT -----
As a general follow up of this question, #Darian Miller started a Google Code project here https://code.google.com/p/delphi-stackoverflow/ which is a working basis. I mark his answer as the "accepted answer" but users should refer to this open source project (all the credit belongs to him) as it will surely be extended and updated in the future.
The root problem is that pings are connectionless traffic. If you have multiple TIdIcmpClient objects pinging the network at the same time, one TIdIcmpClient instance can receive a reply that actually belongs to another TIdIcmpClient instance. You are trying to account for that in your thread loop, by checking SequenceId values, but you are not taking into account that TIdIcmpClient already does that same check internally. It reads network replies in a loop until it receives the reply it is expecting, or until the ReceiveTimeout occurs. If it receives a reply it is not expecting, it simply discards that reply. So, if one TIdIcmpClient instance discards a reply that another TIdIcmpClient instance was expecting, that reply will not get processed by your code, and that other TIdIcmpClient will likely receive another TIdIcmpClient's reply instead, and so on. By adding the Sleep(), you are decreasing (but not eliminating) the chances that pings will overlap each other.
For what you are attempting to do, you won't be able to use TIdIcmpClient as-is to have multiple pings running in parallel, sorry. It is simply not designed for that. There is no way for it to differentiate reply data the way you need it. You will have to serialize your threads so only one thread can call TIdIcmpClient.Ping() at a time.
If serializing the pings is not an option for you, you can try copying portions of TIdIcmpClient's source code into your own code. Have 41 threads running - 40 device threads and 1 response thread. Create a single socket that all of the threads share. Have each device thread prepare and send its individual ping request to the network using that socket. Then have the response thread continuously reading replies from that same socket and routing them back to the appropriate device thread for processing. This is a bit more work, but it will give you the multiple-ping parallelism you are looking for.
If you don't want to go to all that trouble, an alternative is to just use a third-party app that already supports pinging multiple machines at the same time, like FREEPing.
Remy explained the problems... I've wanted to do this in Indy for a while so I posted a possible solution that I just put together to a new Google Code project instead of having a long comment here. It's a first-stab sort of thing, let me know if you have some changes to integrate:
https://code.google.com/p/delphi-vault/
This code has two ways to Ping...multi-threaded clients as in your example, or with a simple callback procedure. Written for Indy10 and later versions of Delphi.
Your code would end up using a TThreadedPing descendant defining a SynchronizedResponse method:
TMyPingThread = class(TThreadedPing)
protected
procedure SynchronizedResponse(const ReplyStatus:TReplyStatus); override;
end;
And to fire off some client threads, the code becomes something like:
procedure TfrmThreadedPingSample.butStartPingClick(Sender: TObject);
begin
TMyPingThread.Create('www.google.com');
TMyPingThread.Create('127.0.0.1');
TMyPingThread.Create('www.shouldnotresolvetoanythingatall.com');
TMyPingThread.Create('127.0.0.1');
TMyPingThread.Create('www.microsoft.com');
TMyPingThread.Create('127.0.0.1');
end;
The threaded response is called in a synchronized method:
procedure TMyPingThread.SynchronizedResponse(const ReplyStatus:TReplyStatus);
begin
frmThreadedPingSample.Memo1.Lines.Add(TPingClient.FormatStandardResponse(ReplyStatus));
end;
I did not try your code, so that is all hypothetical, but i think you messed the threads and got classic race condition. I restate my advice to use AsyncCalls or OmniThreadLibrary - they are much simpler and would save you few attempts at "shooting your own foot".
Threads are made to minimize main-thread load. Thread constructor should do minimal work of remembering parameters. Personally i'd moved idICMP creation into .Execute method. If for any reason it would want to create its internal synchronization objects, like window and message queue or signal or whatever, i'd like it to happen already in a new spawned thread.
There is no sense for "inherited;" in .Execute. Better remove it.
Silencing all exceptions is bad style. You probably have errors - but have no way to know about them. You should propagate them to main thread and display them. OTL and AC help you in that, while for tThread you have to do it manually. How to Handle Exceptions thrown in AsyncCalls function without calling .Sync?
Exception logic is flawed. There is no point to have a loop if exception thrown - if no succesful Ping was set - then why waiting for response ? You loop should go within same try-except frame as issuing ping.
Your doOnPingReply executes AFTER fIdIcmpClient.Free yet accesses fIdIcmpClient's internals. Tried changing .Free for FreeAndNil ?
That is a classic mistake of using dead pointer after freeing it.
The correct approach would be to:
5.1. either free the object in doOnPingReply
5.2. or copy all relevant data from doOnPingReply to TThread's private member vars before calling both Synchronize and idICMP.Free (and only use those vars in doOnPingReply )
5.3. only do fIdIcmpClient.Free inside TMyThread.BeforeDestruction or TMyThread.Destroy. Afterall, if you chosen to create the object in constructor - then you should free it in the matching language construct - destructor.
Since you do not keep references to the thread objects - that While not Terminated loop seems redundant. Just make usual forever-loop and call break.
The aforementioned loop is CPU-hungry, it is like spin-loop. Please call Sleep(0); or Yield(); inside loop to give other threads better chance to do their work. Don't work agaisnt OS scheduler here - you are not in a speed-critical path, no reason to make spinlock here.
Overall, i consider:
4 and 5 as critical bugs for you
1 and 3 as a potential gotcha maybe influencing or maybe not. You'd better 'play safe' rather than doing risky things and investigating if they would work or not.
2 and 7 - bad style, 2 regarding language and 7 regarding platform
6 either you have plans to extend your app, or you broke YAGNI principle, dunno.
Sticking with complex TThread instead of OTL or AsyncCalls - strategic errors. Don't you put rooks on your runway, use simple tools.
Funny, this is example of the bug that FreeAndNil could expose and make obvious, while FreeAndNil-haters are claiming it "conceals" bugs.
// This is my communication unit witch works well, no need to know its work but your
// ask is in the TPingThread class.
UNIT UComm;
INTERFACE
USES
Windows, Messages, SysUtils, Classes, Graphics, Controls, ExtCtrls, Forms, Dialogs,
StdCtrls,IdIcmpClient, ComCtrls, DB, abcwav, SyncObjs, IdStack, IdException,
IdTCPServer, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdContext,
UDM, UCommon;
TYPE
TNetworkState = (nsNone, nsLAN, nsNoLAN, nsNet, nsNoNet);
TDialerStatus = (dsNone, dsConnected, dsDisconnected, dsNotSync);
{ TBaseThread }
TBaseThread = Class(TThread)
Private
FEvent : THandle;
FEventOwned : Boolean;
Procedure ThreadTerminate(Sender: TObject); Virtual;
Public
Constructor Create(AEventName: String);
Property EventOwned: Boolean Read FEventOwned;
End;
.
.
.
{ TPingThread }
TPingThread = Class(TBaseThread)
Private
FReply : Boolean;
FTimeOut : Integer;
FcmpClient : TIdIcmpClient;
Procedure ReplyEvent(Sender: TComponent; Const AReplyStatus: TReplyStatus);
Protected
Procedure Execute; Override;
Procedure ThreadTerminate(Sender: TObject); Override;
Public
Constructor Create(AHostIP, AEventName: String; ATimeOut: Integer);
Property Reply: Boolean Read FReply;
End;
.
.
.
{ =============================================================================== }
IMPLEMENTATION
{$R *.dfm}
USES
TypInfo, WinSock, IdGlobal, UCounter, UGlobalInstance, URemoteDesktop;
{IdGlobal: For RawToBytes function 10/07/2013 04:18 }
{ TBaseThread }
//---------------------------------------------------------
Constructor TBaseThread.Create(AEventName: String);
Begin
SetLastError(NO_ERROR);
FEvent := CreateEvent(Nil, False, False, PChar(AEventName));
If GetLastError = ERROR_ALREADY_EXISTS
Then Begin
CloseHandle(FEvent);
FEventOwned := False;
End
Else If FEvent <> 0 Then
Begin
FEventOwned := True;
Inherited Create(True);
FreeOnTerminate := True;
OnTerminate := ThreadTerminate;
End;
End;
//---------------------------------------------------------
Procedure TBaseThread.ThreadTerminate(Sender: TObject);
Begin
CloseHandle(FEvent);
End;
{ TLANThread }
.
.
.
{ TPingThread }
//---------------------------------------------------------
Constructor TPingThread.Create(AHostIP: String; AEventName: String; ATimeOut: Integer);
Begin
Inherited Create(AEventName);
If Not EventOwned Then Exit;
FTimeOut := ATimeOut;
FcmpClient := TIdIcmpClient.Create(Nil);
With FcmpClient Do
Begin
Host := AHostIP;
ReceiveTimeOut := ATimeOut;
OnReply := ReplyEvent;
End;
End;
//---------------------------------------------------------
Procedure TPingThread.Execute;
Begin
Try
FcmpClient.Ping;
FReply := FReply And (WaitForSingleObject(FEvent, FTimeOut) = WAIT_OBJECT_0);
Except
FReply := False;
End;
End;
//---------------------------------------------------------
Procedure TPingThread.ReplyEvent(Sender: TComponent; Const AReplyStatus: TReplyStatus);
Begin
With AReplyStatus Do
FReply := (ReplyStatusType = rsEcho) And (BytesReceived <> 0);
SetEvent(FEvent);
End;
//---------------------------------------------------------
Procedure TPingThread.ThreadTerminate(Sender: TObject);
Begin
FreeAndNil(FcmpClient);
Inherited;
End;
{ TNetThread }
.
.
.

Call a TDataModule method in TThread.Execute

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.
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.
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.
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.
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.
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.
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

Resources