I want to copy many files in one, but using multiThread,supposing that file A is the file in which different threads copy datas, in this case each thread is meant to copy one file in file A, using this procedure:
procedure ConcatenateFiles(const InFileNames: array of string;
const OutFileName: string);
var
i: Integer;
InStream, OutStream: TFileStream;
begin
OutStream := TFileStream.Create(OutFileName, fmCreate);
try
for i := 0 to high(InFileNames) do
begin
InStream := TFileStream.Create(InFileNames[i], fmOpenRead);
try
OutStream.CopyFrom(InStream, InStream.Size);
finally
InStream.Free;
end;
end;
finally
OutStream.Free;
end;
end;
First, is it possible to realise multithread copy files in this case, because OutFileName is a global variable, two threads can't use it at the same time, and this is the error that i get,
if this is possible how can I synchronise threads to avoid the use of OutFileName by more than one processus in a moment?
And is it really efficient to make a multithread copy files, I'm talking about the speed of copying files.
thanks for your replies
It's perfectly possible to copy files using multiple threads. You would typically use a single producer thread and multiple consumers to do the work. In your case you are concatenating. So you'd need to work out the start and end point of each source file, and then get the threads to write separate parts of the destination file at the pre-calculated positions. Certainly possible.
However, it's not a good idea idea. Multiple threading works well when the job is CPU bound. File copying is disk bound and no amount of extra threads can help. In fact you will likely end up making performance worse because the multiple threads will just get in each others way whilst fighting over the shared disk resource.
If you want to concatenate multiple input files in parallel into a single destination file, you can do it this way:
pre-allocate the destination file. Create the file, seek to the intended final concatenated file size, and set EOF to allocate the file on the file system. With a TFileStream, this can be accomplished by simply setting the TFileStream.Size property to the intended size. Otherwise, using the Win32 API directly, you would have to use CreateFile(), SetFilePointer(), and SetEndOfFile().
Divide up the destination file into logical sections, each with a starting and ending offset within the file, and assign those sections to your threads as needed. Have each thread open its own local handle to the same destination file. That will allow each thread to seek and write independently. Make sure each thread does not leave its assigned section so it does not corrupt another thread's written data.
For example:
type
TFileInfo = record
InFileName: String;
OutFileName: String;
OutFileStart: Int64;
OutFileSize: Int64;
end;
TCopyThread = class(TThread)
protected
FFileInfo: TFileInfo;
procedure Execute;
public
constructor Create(const AFileInfo: TFileInfo);
end;
constructor TCopyThread.Create(const AFileInfo: TFileInfo);
begin
inherited Create(False);
FFileInfo := AFileInfo;
end;
procedure TCopyThread.Execute;
var
InStream: TFileStream;
OutStream: TFileStream;
begin
InStream := TFileStream.Create(FFileInfo.InFileName, fmOpenRead or fmShareDenyWrite);
try
OutStream := TFileStream.Create(FFileInfo.OutFileName, fmOpenWrite or fmShareDenyNone);
try
OutStream.Position := FFileInfo.OutFileStart;
OutStream.CopyFrom(InStream, FFileInfo.OutFileSize);
finally
OutStream.Free;
end;
finally
InStream.Free;
end;
end;
procedure ConcatenateFiles(const InFileNames: array of string; const OutFileName: string);
var
i: Integer;
OutStream: TFileStream;
FileInfo: array of TFileInfo;
TotalSize: Int64;
sr: TSearchRec;
Threads: array of TCopyThread;
ThreadHandles: array of THandle;
NumThreads: Integer;
begin
SetLength(FileInfo, Length(InFileNames));
NumThreads := 0;
TotalSize := 0;
for i := 0 to High(InFileNames) do
begin
if FindFirst(InFileNames[i], faAnyFile, sr) <> 0 then
raise Exception.CreateFmt('Cannot retrieve size of file: %s', [InFileNames[i]]);
if sr.Size > 0 then
begin
FileInfo[NumThreads].InFileName := InFileNames[i];
FileInfo[NumThreads].OutFileName := OutFileName;
FileInfo[NumThreads].OutFileStart := TotalSize;
FileInfo[NumThreads].OutFileSize := sr.Size;
Inc(NumThreads);
Inc(TotalSize, sr.Size);
end;
FindClose(sr);
end;
OutStream := TFileStream.Create(OutFileName, fmCreate);
try
OutStream.Size := TotalSize;
finally
OutStream.Free;
end;
SetLength(Threads, NumThreads);
SetLength(ThreadHandles, NumThreads);
for i := 0 to NumThreads-1 do
begin
Threads[i] := TCopyThread.Create(FileInfo[i]);
ThreadHandles[i] := Threads[i].Handle;
end;
i := 0;
while i < NumThreads do
begin
WaitForMultipleObjects(Min(NumThreads-i, MAXIMUM_WAIT_OBJECTS), ThreadHandles[i], TRUE, INFINITE);
Inc(i, MAXIMUM_WAIT_OBJECTS);
end;
for i := 0 to NumThreads-1 do
begin
Threads[i].Terminate;
Threads[i].WaitFor;
Threads[i].Free;
end;
end;
As it was mentioned already writing to same file from multiple threads is not so good idea.
If you try doing it in a way that multiple threads share same file handle you end up with big problem of making sure that one thread doesent move file position using Seek command while other one is trying to write some data.
If you try doing it in a way that each thread creates its own handle to the file then you end up with the problem that OS doesen't generally alow having multiple file handles with writing capability simuntaniously as this can be recipie for disaster (data coruption).
Now even if you somehow manage to get this working so that each tread is writing in its own section of the file and that they are not messing with each other you will still be losing some performance due to hard drive limitation (HDD head needs to be repositioned into corect place - lots of back and forth movment).
Hey but you could use miltiple threads to go and prepare the final file inside your memory before it is being written on your hard drive. This can be easily done since memory acces is so fast that you practically don't lose any pefrormance by jumping back and forth. The only problem with this is that you could quickly run out of memory if you are concating several larger files.
EDIT: BTW if you are interested I could share a code example of two threaded double-buffered file copy example that I made several years ago. Note it does not provide any data verification capabilities as it was only written to test a theory or shoud I say break a theory that it isn't possible to copy a file only with Delphi (without uising file copy API from Windows). When doing file copy on same HDD it is a bit slower than built in Windows routine but when copying from one HDD to another it reaches same speed as windows built in routines.
Related
I am trying to implement a Pipeline pattern in my test project (How to make a Mutlithreded idhttp calls to do work on a StringList), but am having a struggle adapting TThread code to Pipeline pattern code. There are not many resources about how to use it.
I tried my best below, please DO NOT downvote, I know my code is messy but I'll edit my question if needed.
type
TForm2 = class(TForm)
...
private
procedure Retriever(const input: TOmniValue; var output: TOmniValue);
procedure Inserter(const input, output: IOmniBlockingCollection);
function HttpGet(url: string; var page: string): boolean;
end;
procedure TForm2.startButton1Click(Sender: TObject);
var
pipeline: IOmniPipeline;
i : Integer;
v : TOmniValue;
s : string;
urlList : TStringList;
begin
pipeline := Parallel.Pipeline;
pipeline.Stage(Retriever);
pipeline.Stage(Inserter).NumTasks(10);
pipeline.Run;
for s in urlList do
pipeline.Input.Add(s);
pipeline.Input.CompleteAdding;
// wait for pipeline to complete
pipeline.WaitFor(INFINITE);
end;
function TForm2.HttpGet(url: string; var page: string): boolean;
var
lHTTP: TIdHTTP;
i : integer;
X : Tstrings;
S,M,fPath : String;
begin
lHTTP := TIdHTTP.Create(nil);
X := TStringList.Create;
try
X.Text := lHTTP.Get('https://instagram.com/'+fPath);
S:= ExtractDelimitedString(X.Text);
X.Clear;
Memo2.Lines.Add(fPath+ ' : '+ M ); //how to pass the result to Inserter
finally
lHttp.Free;
end;
end;
procedure TForm2.Inserter(const input, output: IOmniBlockingCollection);
var
result : TOmniValue;
lpage : string;
begin
for result in input do begin
Memo2.Lines.Add(lpage);
FreeAndNil(lpage);
end;
// correect?
end;
procedure TForm2.Retriever(const input: TOmniValue; var output: TOmniValue);
var
pageContents: string;
begin
if HttpGet(input.AsString, pageContents) then
output := //???
end;
First of all - describe what is your specific problem. No one can stand behind your back and look at your computer and see what you are doing.
http://www.catb.org/esr/faqs/smart-questions.html#beprecise
You do imply your program misbehaves. But you do not describe how and why. And we do not know it.
As general remarks, you overuse the pipeline a bit.
all the worker procedures you pass to OTL - in your case those are Inserter and Retriever work in random threads. That means none of them should touch GUI without synchronizing - VCL is not multithreaded.
Also using TThread.Synchronize is a poor choice as I explained to you in the linked question. It makes program slow and it makes forms unreadable. To update your form use polling with fixed framerate. Do not update your form from inside OTL workers.
In other words, Inserter is not what you need. All you need from the pipeline here is its Input collection, a downloader procedure and the Output collection. Yes it is very simple task for the complex things pipelines are, that is why I mentioned two other simpler patterns before it.
You need TTimer on your form that would poll the Output collection at fixed framerate 2-3 times per second, and check that the collection is not finalized yet ( if it is - the pipeline got stopped ) and that should update GUI from a main thread.
You should not wait for a pipeline to finish inside your main VCL thread. Instead You should detach the pipeleine and let it run totally in background. Save the reference to the created pipeline into the Form's member variable so you could access its Output collection from the TTimer event and also can free the pipeline after its process run over.
You should keep that variable linked to the pipeline object until the downloading is over and set to nil (Free the objects) after that, but not before. You know about interfaces and reference-counting in Delphi, right?
For other OTL patterns like parallel-FOR read OTL docs about their .NoWait() calls.
You should make this Your form bi-modal, to have different set of enabled controls when downloading is running and when it is not. I usually do it with special Boolean property like I shown to you in the topic you linked.
Your user is not supposed to change the lists and settings while the pipeline is in progress (unless you would implement that realtime task changing, but you did not yet). This mode switcher would also be a good place to free the finished pipeline object when the switching is going from working mode to idle mode.
If you would want to play with the pipeline workers chaining, then you can put into the Input Collection not the URL strings themselves, but the array of those - the Memo1.Lines.ToArray(), then you can start with Unpacker stage that gets string arrays from the input collection (there would be only one, actually) and enumerate it and put the strings into stage-output collection.
This however has little practical value, it would even slow your program down a tiny bit, as the Memo1.Lines.ToArray() function would still work in the main VCL thread. But just to experiment with the pipelines this might be funny.
So the draft becomes like that,
TfrmMain = class(TForm)
private
var pipeline: IOmniPipeline;
property inProcess: Boolean read ... write SetInProcess;
...
end.
procedure Retriever(const input: TOmniValue; var output: TOmniValue);
var
pageContents, URL: string;
lHTTP: TIdHTTP;
begin
URL := input.AsString;
lHTTP := TIdHTTP.Create(nil);
try
lHTTP.ReadTimeout := 30000;
lHTTP.HandleRedirects := True;
pageContents := ExtractDelimitedString( lHTTP.Get('https://instagram.com/' + URL) );
if pageContents > '' then
Output := pageContents;
finally
lHTTP.Destroy;
end;
end;
procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if InProgress then begin
CanClose := False;
ShowMessage( 'You cannot close this window now.'^M^J+
'Wait for downloads to complete first.' );
end;
end;
procedure TfrmMain.SetInProcess(const Value: Boolean);
begin
if Value = InProcess then exit; // form already is in this mode
FInProcess := Value;
memo1.ReadOnly := Value;
StartButton.Enabled := not Value;
if Value then
Memo2.Lines.Clear;
Timer1.Delay := 500; // twice per second
Timer1.Enabled := Value;
If not Value then // for future optimisation - make immediate mode change
FlushData; // when last worker thread quits, no waiting for timer event
If not Value then
pipeline := nil; // free the pipeline object
If not Value then
ShowMessage('Work complete');
end;
procedure TfrmMain.Timer1Timer(const Sender: TObject);
begin
If not InProcess then exit;
FlushData;
if Pipeline.Output.IsFinalized then
InProcess := False;
end;
procedure TForm2.startButton1Click(Sender: TObject);
var
s : string;
urlList : TStringList;
begin
urlList := Memo1.Lines;
pipeline := Parallel.Pipeline;
pipeline.Stage(Retriever).NumTasks(10).Run;
InProcess := True; // Lock the input data GUI - user no more can edit it
for s in urlList do
pipeline.Input.Add(s);
pipeline.Input.CompleteAdding;
end;
procedure TfrmMain.FlushData;
var v: TOmniValue;
begin
if pipeline = nil then exit;
if pipeline.Output = nil then exit;
if pipeline.Output.IsFinalized then
begin
InProcess := False;
exit;
end;
Memo2.Lines.BeginUpdate;
try
while pipeline.Output.TryTake(v) do
Memo2.Lines.Add( v.AsString );
finally
Memo2.Lines.EndUpdate;
end;
// optionally - scroll output memo2 to the last line
end;
Note few details, think about them and understand the essence of those:
Only FlushData is updating the output memo. FlushData is called from the TTimer event or from the form mode property setter. Both of them only are ever called from the main VCL thread. Thus FlushData is NEVER called form background threads.
Retriever is a free standalone function, it is not a member of the form and it knows nothing about the form and has no reference to your form instance(s). That way you achieve both goals: you avoid "tight coupling" and you avoid a chance of mistakingly access the form's controls from a background thread, which is not allowed in VCL.
Retriever functions work in background threads, they do load the data, they do store the data, but they never touch the GUI. That is the idea.
Rule of thumb - all methods of the form are only called from the main VCL thread. All pipeline stage subroutines - bodies of the background threads - are declared and work outside of any VCL forms and have no access to none of those. There should be no mix between those realms.
you throttle GUI update to a fixed refresh rate. And that rate should be not too frequent. Windows GUI and user eyes should have time to catch up.
Your form operates in two clearly delineated modes - InProcess and not InProcess. In those modes different sets of functions and controls are available to the user. It also manages mode-to-mode transitions like clearing output-memo text, alerting user of status changes, freeing memory of used threads-managing objects (here: pipelines), etc. Consequently, this property only is changed (setter is called) from main VCL thread, never from background workers. And #2 helps with that too.
The possible future enhancement would be to use pipeline.OnStop event to issue a PostMessage with a custom Windows Message to your form, so it would switch the mode immediately as the work is done, not waiting for the next timer olling event. This might be the ONLY place where pipeline knows anything about the form and has any references to it. But this open the can of Windows messaging, HWND recreation and other subtle things that I do not want to put here.
I want to write from multiple threads/processes to a TListBox called 'listMessages' and I have this two procedures in order to do this :
1- With adding object :
procedure Log(Msg: String; Color: TColor);
begin
listMessages.Items.AddObject(Msg, Pointer(Color));
listMessages.ItemIndex := listMessages.Items.Count -1;
end;
2- With TIdCriticalSection called protectListMessages :
procedure TMainForm.safelyLogMessage(mess : String);
begin
protectlistMessages.Enter;
try
listMessages.Items.Add(mess);
listMessages.ItemIndex := listMessages.Items.Count -1;
finally
protectListMessages.Leave;
end;
end;
Can you tell me which is best(fast + thread safe) or show me a third way to write messages to my TListBox from my threads/processes ?
Neither of your options is any good. You need to use option 3!
The point is that all access to UI controls must execute on the main thread. Use TThread.Synchronize or TThread.Queue to marshal UI code onto the main UI thread. Once you do this, the code will not need any further serialization because the very act of getting it to run on the UI thread serializes it.
The code might look like this:
procedure TMainForm.Log(const Msg: string; const Color: TColor);
var
Proc: TThreadProcedure;
begin
Proc :=
procedure
begin
ListBox1.AddItem(Msg, Pointer(Color));
ListBox1.ItemIndex := ListBox1.Count-1;
end;
if GetCurrentThreadId = MainThreadID then
Proc()
else
TThread.Queue(nil, Proc);
end;
In your update you state that you need to write to the list box from a different process. This cannot be achieved with any of the code in the question. You need inter-process communication (IPC) for that. Sending windows messages would be a reasonable approach to take, but there are other IPC options available. But I think that you mis-speak when you use the term process. I suspect that you don't mean process, but what you do mean, I have no idea.
I want to write from multiple threads/processes to a TListBox called 'listMessages' and I have this two procedures in order to do this :
1- With adding object :
procedure Log(Msg: String; Color: TColor);
begin
listMessages.Items.AddObject(Msg, Pointer(Color));
listMessages.ItemIndex := listMessages.Items.Count -1;
end;
2- With TIdCriticalSection called protectListMessages :
procedure TMainForm.safelyLogMessage(mess : String);
begin
protectlistMessages.Enter;
try
listMessages.Items.Add(mess);
listMessages.ItemIndex := listMessages.Items.Count -1;
finally
protectListMessages.Leave;
end;
end;
Can you tell me which is best(fast + thread safe) or show me a third way to write messages to my TListBox from my threads/processes ?
Neither of your options is any good. You need to use option 3!
The point is that all access to UI controls must execute on the main thread. Use TThread.Synchronize or TThread.Queue to marshal UI code onto the main UI thread. Once you do this, the code will not need any further serialization because the very act of getting it to run on the UI thread serializes it.
The code might look like this:
procedure TMainForm.Log(const Msg: string; const Color: TColor);
var
Proc: TThreadProcedure;
begin
Proc :=
procedure
begin
ListBox1.AddItem(Msg, Pointer(Color));
ListBox1.ItemIndex := ListBox1.Count-1;
end;
if GetCurrentThreadId = MainThreadID then
Proc()
else
TThread.Queue(nil, Proc);
end;
In your update you state that you need to write to the list box from a different process. This cannot be achieved with any of the code in the question. You need inter-process communication (IPC) for that. Sending windows messages would be a reasonable approach to take, but there are other IPC options available. But I think that you mis-speak when you use the term process. I suspect that you don't mean process, but what you do mean, I have no idea.
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 }
.
.
.
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