Drawing to main form canvas from a non-main thread - multithreading

I am trying to make an arcade game for my school project. The basic idea is to do all the math and drawing in other thread than the main, and to use the main thread only for input routines. Drawing is done by a procedure saved in an external unit, and is done by creating a bitmap, then drawing parts of the environment on the bitmap and finally dawing the bitmap on the main form's canvas. When I finished the drawing procedure, I tried to run it from the main thread, and managed to make everythink work as expected (except for the fact that the whole application window was frozen, but since the main thread was working without stopping, sommething like that was expected). Then i tried to put the procedure in other thread, and it stopped working (it didnt draw a single thing despite debug routines reporting that the procedure was repeatedly executed). After a few added and then deleted debug routnes, it started to work for no apparent reason, but not reliably. in about 80% of cases it runs smoothly, but in the rest it stops after ten to thirty frames, sometimes even not draving some of the environment parts in the last frame where it gets stuck.
The important part of the main form unit looks like this
procedure TForm1.Button1Click(Sender: TObject);
begin
running:=not running;
if running then AppTheard.Create(false);
end;
Procedure AppTheard.execute;
begin
form1.Button1.Caption:='running';
while running do begin view.nextframe; end;
form1.Button1.Caption:='no longer running';
end;
and the nextframe procedure in the other unit looks like this
Camera = class
owner:Tform;
focus:GravityAffected;
Walls:PBlankLevel;
Creeps:MonsterList;
FrameRateCap,lastframe:integer;
Background:TBitmap;
plocha:TBitmap;
RelativePosY,RelativePosX:integer;
constructor create(owner:Tform; focus:GravityAffected; Walls:PBlankLevel; Creeps:MonsterList; FrameRateCap:integer; background:TBitmap);
procedure nextframe;
end;
procedure camera.nextframe;
var i,i1,top,topinfield, left,leftinfield: integer ;
procedure Repair
//some unimportant math here
Procedure vykresli(co:vec);
begin
if co is gravityaffected then
plocha.Canvas.Draw(co.PositionX*fieldsize+Gravityaffected(co).PosInFieldX-Left*fieldsize+leftinfield-co.getImgPosX,
co.PositionY*fieldsize+Gravityaffected(co).PosInFieldY-top*fieldsize+topinfield-co.getImgPosY,
co.image)
else
plocha.Canvas.Draw(co.PositionX*fieldsize-Left*fieldsize+leftinfield-co.getImgPosX,
co.PositionY*fieldsize-top*fieldsize+topinfield-co.getImgPosY,
co.image);
end;
begin
// some more unimportant math
vykresli(focus);
For i:= Left+1 to left+2+(plocha.Width div fieldsize) do //vykreslení zdí
For i1:= Top+1 to top+2+(plocha.Height div fieldsize) do
if (i< Walls.LevelSizeX) and (i1< Walls.LevelSizeY) and (i>=0) and (i1>=0) and walls.IsZed(i,i1) then
begin vykresli(walls^.GiveZed(i,i1)^);end;
while abs((gettickcount() mod high(word))-lastframe) < (1000 div FrameRateCap) do sleep(1);
lastframe:=gettickcount mod high (word);
owner.Canvas.Draw(-fieldsize,-fieldsize,plocha);
end;
Can someone please tell me what am I doing wrong?
Edit: I got the help I asked for, but after a few more years, I realized that the advice I really needed was not to use threads at all and try something like this instead.

I see a number of things wrong in your approach at this.
1) All VCL interaction must be done from within the main thread
Your thread is directly accessing VCL controls. You cannot do this, as VCL is not thread-safe. You have to synchronize all your events back to the main thread, and let the main thread do this work.
2) All custom UI drawing (to the form) must be done from within the form's OnPaint event.
This explains why it works sometimes and not other times. The form is automatically painted, and if you don't use this event, your custom drawing will just be drawn over by the VCL.
3) All UI drawing must be done from within the main thread
This brings us back to points 1 and 2. VCL is not thread-safe. Your secondary thread should only be responsible for performing calculations, but not drawing the UI. After performing some calculation or doing some lengthy work, you must synchronize the results back to the main thread, and let that main thread do the drawing.
4) The thread should be entirely self-contained
You shouldn't put any code in this secondary thread which has any knowledge of how it will be displayed. In your case, you are explicitly referencing the form. Your thread should not even know if it's being used by a form. Your thread should only perform the lengthy calculation work, and have absolutely 0 consideration of the user interface. Synchronize events back to your main form when you need to instruct it to redraw.
Conclusion
You need to research thread safety. You will be able to answer most of your own questions by doing so. Make this thread strictly only to take care of the heavy work which would otherwise bog down the UI. Don't worry much about a slow UI, most modern computers are able to perform complex drawing in a small fraction of a second. That doesn't need to be in a separate thread.
EDIT
After a few more years of experience, I've come to realize that #3 above is not necessarily true. In fact, in many cases, it's a great approach to perform detailed drawing from within a thread, but then the main thread would only be responsible for rendering that image to the user.
That, of course, is a whole topic of its own. You need to be able to safely paint the image which is managed in one thread to the other thread. This, also, requires use of Synchronize.

Create a TThread subclass and pass all variables needed in the constructor.
Store these variables in the private section of your TThread subclass.
Create and Free them as needed in the constructor and destructor.
Create an event handler (ex OnThreadPaint). You may pass it in the Constructor too.
As mentioned your thread must be entirely self-contained (variables, code, etc).
Place your code in the Execute procedure of the thread. You may construct a Bitmap or whatever, draw on it (as you would do in your actual (ex TForm's) canvas. Remember to resize the TBitmap instance.
Finally, Call the Synchronized method passing the event handler (OnThreadPaint). This will execute the event in main thread.
This can be considered as 'double buffer' method, which will prevent any flickering on drawing.
So...
TDrawThread = class(TThread)
private
FOnThreadPaint: TNotifyEvent;
FVar: Integer;
FBitamp: TBitmap;
protected
procedure Execute; override;
procedure SynchProc;
public
constructor Create(aVar: Integer; onPaint:TNotifyEvent);
destructor Destroy; override;
property Bmp:TBitMap read FBitMap;
end;
constructor TDrawThread.Create(aVar: Integer; onPaint:TNotifyEvent);
begin
inherited Create(False);
FreeOnTerminate := True;
FVar := aVar;
FOnThreadPaint := onPain;
FBitMap := TBitMap.Create;
// FVarOther := TVarOther.Create;
// FVarOther.. assign
end;
destructor TDrawThread.Destroy;
begin
FBitMap.Free;
// FVarOther.Free;
inherited;
end;
procedure TDrawThread.Execute;
begin
FBitMap.width := ..
FBitMap.height := ..
// do more Drawing on the FBitmpap here
if Assigned(FOnThreadPaint) then Synchronize(SynchProc);
end;
procedure TDrawThread.SynchProc;
begin
FOnThreadPaint(Self);
end;
And in your main form...
TForm1 = class(TForm)
private
{ Private declarations }
procedure onMyPaint(Sender: TObject);
...
procedure TForm1.onMyPaint(Sender: TObject);
begin
with Sender as TDrawThread do begin
Canvas.Draw(0, 0, Bmp);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
TDrawThread.Create(30, onMyPaint);
end;

Related

Some threads does not execute after creating on Multi-threaded app

I've currently developing a program which executes some AT commands periodically.
I tried to make it multi-threaded because this program should work with 8 GSM modems concurrently.
here is my extended TThread class as TWorkerThread:
TWorkerThread = class(TThread)
private
FThreadJob : TThreadJobs;
FSimNum : Word;
FZylGSM : TZylGSM;
SL_AT : TStringList;
FSignalGauge : TsGauge;
procedure SyncProc;
public
TerminateThread : Boolean;
constructor Create;
property ThreadJob : TThreadJobs read FThreadJob write FThreadJob;
property ZylGSM : TZylGSM read FZylGSM write FZylGSM;
property SimNum : Word read FSimNum write FSimNum;
property SignalGauge: TsGauge read FSignalGauge write FSignalGauge;
protected
procedure Execute; override;
end;
And body of my thread's methods :
constructor TWorkerThread.Create;
begin
inherited Create(True);
if Not Assigned(SL_AT) then SL_AT := TStringList.Create;
SL_AT.Clear;
FThreadJob := tjNone;
TerminateThread := False;
FreeOnTerminate := True;
end;
procedure TWorkerThread.Execute;
begin
inherited;
if FThreadJob = tjNone then Exit;
while TerminateThread=False do Synchronize(SyncProc);
end;
procedure TWorkerThread.SyncProc;
var
ts : String;
SignalStrength : Byte;
begin
if bTerminateFlag then TerminateThread := True;
if TerminateThread then Exit;
case FThreadJob of
tjOperatorName : ;
tjSignalQuality :
begin
FZylGSM.ExecuteATCommand('AT+CSQ', SL_AT);
if (SL_AT.Count>2) And (Pos('OK', SL_AT[2])>0) then begin
ts := Copy(SL_AT[1], Pos(':', SL_AT[1])+1, Length(SL_AT[1]));
ts := Trim(ts);
if ts = '99' then ts:='0';
SignalStrength := StrToIntDef(ts, 0);
SignalGauge.Progress := SignalStrength;
end;
if bTerminateFlag then TerminateThread := True;
// Application.ProcessMessages;
end;
end;
end;
I've used a for loop to create 8 threads like this:
DevPorts.GSM_Ports[i].WorkerThread := TWorkerThread.Create;
DevPorts.GSM_Ports[i].WorkerThread.ThreadJob := tjSignalQuality;
DevPorts.GSM_Ports[i].WorkerThread.SimNum := i+1;
DevPorts.GSM_Ports[i].WorkerThread.SignalGauge := FindComponent('Sig_'+IntToStr(i)) as TsGauge;
DevPorts.GSM_Ports[i].WorkerThread.ZylGSM := DevPorts.GSM_Ports[i].Comm;
DevPorts.GSM_Ports[i].WorkerThread.Start;
The program worked as expected when I commented "Application.ProcessMessages", the problem is when I use "Application.ProcessMessages" in "TWorkerThread.SyncProc", some of my threads don't executes. I know it could be wrong to use ProcessMessage in the thread function but I did it because the Main GUI thread hangs up during send/recv of threads.
Any help will be appreciated.
Do not call Application.ProcessMessages from your threads. This is a terrible thing to do. The best you can hope for is spectacular failures. It calls code that should be run on the main thread on the wrong threads.
The reason your main GUI thread hangs is because you're not running anything multi-threaded. The line while TerminateThread=False do Synchronize(SyncProc); is synchronising everything to run back on the main thread. So currently your threads are pointless.
The purpose of Synchronize() is to allow threads to coordinate access to shared data so you don't have to deal with race conditions. However, the ideal is to share as little data as possible so that your threads can work independently of each other (and the main thread), without having to worry values under its control being changed at inappropriate times.
So alarm bells are screaming when you make most of your worker thread's members public:
public
TerminateThread : Boolean;
constructor Create;
property ThreadJob : TThreadJobs read FThreadJob write FThreadJob;
property ZylGSM : TZylGSM read FZylGSM write FZylGSM;
property SimNum : Word read FSimNum write FSimNum;
property SignalGauge: TsGauge read FSignalGauge write FSignalGauge;
You need to reevaluate what the responsibilities of your worker threads are and encapsulate that work appropriately. (Only call Synchronize() for code that should be synchronised!) However, I'm not familiar with the components you're using, and you may find that they're poorly written and not suitable for multi-threading as a result.
Other problems
Apart from the glaring immediate problems you have. There are also a number of mistakes demonstrating gaps in your understanding of multi-threaded development.
Do not call inherited from TWorkerThread.Execute. The ancestor method is abstract - has no implementation an cannot be called. And even though the Delphi compiler generously protects you from your mistake, it's a mistake nonetheless.
Your implementation of TerminateThread : Boolean; replicates existing functionality built into TThread. Instead of reinventing the wheel, use what Delphi has already provided.
I cannot see where you declared or are setting bTerminateFlag. My hunch is it's global. Using globals with multiple threads is like juggling flaming torches while standing in a room filled with open barrels of gunpowder.
Some guesses
I can hazard some guesses based on what you are trying to do in the code you have shown.
It looks like you're updating TsGuage instances to visually indicate the signal strength for each device. This is a GUI update that must be synchronised.
The line FZylGSM.ExecuteATCommand('AT+CSQ', SL_AT); seems to be the place where you interact with the device. It's probably also the slowest and what you want to processes off the main thread. This should not be synchronised if possible. However, it as indicated earlier, the feasibility of doing so depends on the implementation of that component.
That said, it seems the only line you should be synchronising is: SignalGauge.Progress := SignalStrength;.

Threads and Critical Section correct approach

Right now have a multi-thread scheme like this:
//global variables
var
Form1: TForm1;
ControlFile: TextFile;
MaxThreads, iThreads: integer;
MyCritical: TCriticalSection;
The ControlFile is accessed by the threads, that do a ReadLn, and perform actions with the line obtained:
procedure TForm1.Button2Click(Sender: TObject);
var
HostLine: AnsiString;
FileHandle: integer;
begin
MyCritical:= TCriticalSection.Create;
MaxThreads:= 100;
iThreads:= 0;
while not(eof(ControlFile)) and (iThreads < MaxThreads) do
begin
inc(iThreads);
ReadLn(ControlFile, HostLine);
MyThread.Create(HostLine);
end;
end;
this block is the first doubt. I'm creating 100 threads, each one created received the current line of the textfile. But the problem is that on threads.onterminate, I execute this:
procedure MyThread.MainControl(Sender: TObject);
var
HostLine: string;
begin
try
MyCritical.Acquire;
dec(iThreads);
while not(eof(ControlFile)) and (iThreads < MaxThreads) do
begin
inc(iThreads);
ReadLn(ControlFile, HostLine);
MyThread.Create(HostLine);
end;
finally
MyCritical.Release;
end;
end;
The idea is to keep creating new threads, until the textfile finishes. But if one thread terminate, and execute this procedure, before the first while finished, what happens? The main thread, from button2click will be accessing the file, and the thread's procedure too. This looks strange to me. And the Critical Section, should be global or thread local? And this procedure, MainControl, that opens new threads until the end of the file, should be global or thread local?
First of all, I am not sure it's such a stellar idea to have different threads read from the same text file. It's not that it can't work, but I think it would be much cleaner to simply read the whole thing into a TStringList variable up front, which can then be shared among threads, if needed.
If you do go with what you already have, your critical section must be acquired in the main loop also - the threads that you spawn will start executing immediately by default, so it looks like there could be a race between your main thread and the threads that run MainControl, though you don't show exactly how that call is going to be made.
The critical section needs to be a global variable, as you have it, or a field/property of a global class in order to be shared among threads.
My final point is that it's probably not the greatest idea to create 100 threads either. Unless your threads are mostly waiting on I/O or events, you should generally not have more threads than you have CPU cores. It's better to use a pool of worker threads and a queue of work items that can then be doled out to the running threads. There's supposedly built-in support for some of this in more recent Delphi RTLs. I personally use my own tried and true custom thread pool implementation, so I can't give you any specific help with that part.
The call to OnTerminate is already synchronised.
procedure TThread.DoTerminate;
begin
if Assigned(FOnTerminate) then Synchronize(CallOnTerminate);
end;
So the critical section is not actually needed at all because all the code you've shown runs in the context of the main thread.

Thread.FreeOnTerminate := True, memory leak and ghost running

Years ago, I decided never to rely solely on setting a thread's FreeOnTerminate property to true to be sure of its destruction, because I discovered and reasoned two things at application's termination:
it produces a memory leak, and
after program's termination, the thread is still running somewhere below the keyboard of my notebook.
I familiarized myself with a workaround, and it did not bother me all this time. Until tonight, when again someone (#MartinJames in this case) commented on my answer in which I refer to some code that does not use FreeOnTerminate in combination with premature termination of the thread. I dove back in the RTL code and realized I may have made the wrong assumptions. But I am not quite sure about that either, hence this question.
First, to reproduce the above mentioned statements, this illustrative code is used:
unit Unit3;
interface
uses
Classes, Windows, Messages, Forms;
type
TMyThread = class(TThread)
FForm: TForm;
procedure Progress;
procedure Execute; override;
end;
TMainForm = class(TForm)
procedure FormClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FThread: TMyThread;
end;
implementation
{$R *.dfm}
{ TMyThread }
procedure TMyThread.Execute;
begin
while not Terminated do
begin
Synchronize(Progress);
Sleep(2000);
end;
end;
procedure TMyThread.Progress;
begin
FForm.Caption := FForm.Caption + '.';
end;
{ TMainForm }
procedure TMainForm.FormClick(Sender: TObject);
begin
FThread := TMyThread.Create(True);
FThread.FForm := Self;
FThread.FreeOnTerminate := True;
FThread.Resume;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
FThread.Terminate;
end;
end.
Now (situation A), if you start the thread with a click on the form, and close the form right after the caption changed, there is a memory leak of 68 bytes. I assume this is because the thread is not freed. Secondly, the program terminates immediately, and the IDE is at that same moment back again in normal state. That in contrast to (situation B): when not making use of FreeOnTerminate and the last line of the above code is changed into FThread.Free, it takes (max.) 2 seconds from the disappearance of the program to the normal IDE state.
The delay in situation B is explained by the fact that FThread.Free calls FThread.WaitFor, both which are executed in the context of the main thread. Further investigation of Classes.pas learned that the destruction of the thread due to FreeOnTerminate is done in the context of the worker thread. This lead to the following questions on situation A:
Is there indeed a memory leak? And if so: is it important, could it be ignored? Because when an application terminates, doesn't Windows give back all its reserved resources?
What happens with the thread? Does it indeed run further somewhere in memory until its work is done, or not? And: is it freed, despite the evidence of the memory leak?
Disclaimer: For memory leak detection, I use this very simple unit as first in the project file.
Indeed, the OS reclaims all a process's memory when it terminates, so even if those 68 bytes refer to the non-freed thread object, the OS is going to take those bytes back anyway. It doesn't really matter whether you've freed the object at that point.
When your main program finishes, it eventually reaches a place where it calls ExitProcess. (You should be able to turn on debug DCUs in your project's linker options and step through to that point with the debugger.) That API call does several things, including terminating all other threads. The threads are not notified that they're terminating, so the cleanup code provided by TThread never runs. The OS thread simply ceases to exist.

Synchronizing/sending data between threads

The app is written in Delphi XE.
I have two classes, a TBoss and TWorker, which are both based of of TThread.
The TBoss is a single instance thread, which starts up and then will create about 20 TWorker threads.
When the boss creates a instance of TWorker it assigns it a method to call synchronize on, when the Worker has finished with what it's doing it calls this method which allows the Boss to access a record on the Worker.
However I feel this is a problem, calling synchronize appears to be locking up the whole application - blocking the main (ui) thread. Really it should just be synchronizing that worker to the boss thread....
Previously I used messages/packed records to send content between threads which worked well. However doing it this way is much cleaner and nicer.... just very blocking.
Is there a way to call Syncronize in the worker to only wait for the Boss thread?
My code:
type
TWorker = class(TThread)
private
fResult : TResultRecord;
procedure SetOnSendResult(const Value: TNotifyEvent);
....
....
public
property OnSendResult: TNotifyEvent write SetOnSendResult;
property Result : TResultRecord read fResult;
....
end;
...
...
procedure TWorker.SendBossResults;
begin
if (Terminated = False) then
begin
Synchronize(SendResult);
end;
end;
procedure TWorker.SendResult;
begin
if (Terminated = false) and Assigned(FOnSendResult) then
begin
FOnSendResult(Self);
end;
end;
Then in my Boss thread I will do something like this
var
Worker : TWorker;
begin
Worker := TWorker.Create;
Worker.OnTerminate := OnWorkerThreadTerminate;
Worker.OnSendResult := ProcessWorkerResults;
So my boss then has a method called ProcessWorkerResults - this is what gets run on the Synchronize(SendResult); of the worker.
procedure TBoss.ProcessWorkerResults(Sender: TObject);
begin
if terminated = false then
begin
If TWorker(Sender).Result.HasRecord then
begin
fResults.Add(TWorker(Sender).Result.Items);
end;
end;
end;
Synchronize is specifically designed to execute code in the main thread; that's why it seems to lock everything up.
You can use several ways to communicate from the worker threads to the boss thread:
Add a callback to each worker thread,
and assign it from the boss thread
when it's created. It can pass back
whatever as parameters, along with a
thread ID or some other identifier.
Post a message from the worker thread
to the boss thread using
PostThreadMessage. The
disadvantage here is that the boss
thread has to have a window handle
(see Classes.AllocateHWnd in the
Delphi help and David Heffernan's comment below).
Use a good quality third-party
threading library. See
OmniThreadLibrary - it's free,
OS, and extremely well written.
My choice would be the third. Primoz has done all the hard work for you. :)
After your comment, here's something along the lines of my first suggestion. Note that this is untested, since writing the code for a TBoss and TWorker thread + a test app is a little long for the time I have right this minute... It should be enough to give you the gist, I hope.
type
TWorker = class(TThread)
private
fResult : TResultRecord;
fListIndex: Integer;
procedure SetOnSendResult(const Value: TNotifyEvent);
....
....
public
property OnSendResult: TNotifyEvent write SetOnSendResult;
property Result : TResultRecord read fResult;
property ListIndex: Integer read FListIndex write FListIndex;
....
end;
type
TBoss=class(TThread)
private
FWorkerList: TThreadList; // Create in TBoss.Create, free in TBoss.Free
...
end;
procedure TWorker.SendBossResults;
begin
if not Terminated then
SendResult;
end;
procedure TBoss.ProcessWorkerResults(Sender: TObject);
var
i: Integer;
begin
if not terminated then
begin
If TWorker(Sender).Result.HasRecord then
begin
FWorkerList.LockList;
try
i := TWorker(Sender).ListIndex;
// Update the appropriate record in the WorkerList
TResultRecord(FWorkerList[i]).Whatever...
finally
FWorkerList.UnlockList;
end;
end;
end;
end;
You could use a thread safe queue. In DelphiXE there is the TThreadedQueue. If you don't have DXE, try OmniThreadLibray - this library is very good for all threading issues.
As I mentioned new options in Delphi 2009 and higher, here is a link to an example for Producer / Consumer communication between threads, based on the new objct locks, in my blog:
Thread Synchronization with Guarded Blocks in Delphi
In a note regarding the deprecated methods TThread.Suspend and
TThread.Resume, The Embarcadero DocWiki for Delphi
recommends that “thread
synchronization techniques should be
based on SyncObjs.TEvent and
SyncObjs.TMutex.“ There is, however,
another synchronization class
available since Delphi 2009: TMonitor.
It uses the object lock which has been
introduced in this version ...
public properties of the TWorker class MUST have get and set methods, so you can use a Tcriticalsection to give the values of the properties. Otherwise, you´d be having thread-safe issues. Your example seems ok, but in the real world, with thousands of threads accessing to the same value would result in an read error. Use critical sections.. and you wouldn´t have to use any Synchronize. This way you avoid going to the message queues of windows and improve performance. Besides, if you use this code in a windows service app, (where windows messages aren´t allowed), this example wouldn´t work. The synchronize method doesn´t work unless there´s access to the windows message queue.
Solved!! (answer taken from the question)
The fixes made for this problem where two fold.
First remove the syncronization call in the TWorker SendBossResult method.
Second add a fProcessWorkerResult CritialSection to TBoss class. Create and Free this in create/destroy of the TBoss. In the ProcessWorkerResults method call fProcessWorkerResult.Enter and fProcessWorkerResult.leave around the code which needs to be safe from multiple worker results streaming in.
The above was the conclusion after Kens code and follow up comment. Many thanks kind sir, hats off to you!.

Free a TThread either automatically or manually

I have a main thread and a separate thread in my program. If the separate thread finishes before the main thread, it should free itself automatically. If the main thread finishes first, it should free the separate thread.
I know about FreeOnTerminate, and I've read that you have to be careful using it.
My question is, is the following code correct?
procedure TMyThread.Execute;
begin
... Do some processing
Synchronize(ThreadFinished);
if Terminated then exit;
FreeOnTerminate := true;
end;
procedure TMyThread.ThreadFinished;
begin
MainForm.MyThreadReady := true;
end;
procedure TMainForm.Create;
begin
MyThreadReady := false;
MyThread := TMyThread.Create(false);
end;
procedure TMainForm.Close;
begin
if not MyThreadReady then
begin
MyThread.Terminate;
MyThread.WaitFor;
MyThread.Free;
end;
end;
You can simplify this to:
procedure TMyThread.Execute;
begin
// ... Do some processing
end;
procedure TMainForm.Create;
begin
MyThread := TMyThread.Create(false);
end;
procedure TMainForm.Close;
begin
if Assigned(MyThread) then
MyThread.Terminate;
MyThread.Free;
end;
Explanation:
Either use FreeOnTerminate or free the thread manually, but never do both. The asynchronous nature of the thread execution means that you run a risk of not freeing the thread or (much worse) doing it twice. There is no risk in keeping the thread object around after it has finished the execution, and there is no risk in calling Terminate() on a thread that has already finished either.
There is no need to synchronize access to a boolean that is only written from one thread and read from another. In the worst case you get the wrong value, but due to the asynchronous execution that is a spurious effect anyway. Synchronization is only necessary for data that can not be read or written to atomically. And if you need to synchronize, don't use Synchronize() for it.
There is no need to have a variable similar to MyThreadReady, as you can use WaitForSingleObject() to interrogate the state of a thread. Pass MyThread.Handle as the first and 0 as the second parameter to it, and check whether the result is WAIT_OBJECT_0 - if so your thread has finished execution.
BTW: Don't use the OnClose event, use OnDestroy instead. The former isn't necessarily called, in which case your thread would maybe continue to run and keep your process alive.
Have the main thread assign a handler to the worker thread's OnTerminate event. If the worker thread finishes first, then the handler can signal the main thread to free the thread. If the main thread finishes first, it can terminate the worker thread. For example:
procedure TMyThread.Execute;
begin
... Do some processing ...
end;
procedure TMainForm.Create;
begin
MyThread := TMyThread.Create(True);
MyThread.OnTerminate := ThreadFinished;
MyThread.Resume; // or MyThread.Start; in D2010+
end;
const
APPWM_FREE_THREAD = WM_APP+1;
procedure TMainForm.ThreadFinished(Sender: TObject);
begin
PostMessage(Handle, APPWM_FREE_THREAD, 0, 0);
end;
procedure TMainForm.WndProc(var Message: TMessage);
begin
if Message.Msg = APPWM_FREE_THREAD then
StopWorkerThread
else
inherited;
end;
procedure TMainForm.StopWorkerThread;
begin
if MyThread <> nil then
begin
MyThread.Terminate;
MyThread.WaitFor;
FreeAndNil(MyThread);
end;
end;
procedure TMainForm.Close;
begin
StopWorkerThread;
end;
No, your code is not good (though it probably will work in 99.99% or even 100% cases). If you are planning to terminate work thread from main thread, don't set FreeOnTerminate to True (I don't see what are you trying to gain in the above code by setting FreeOnTerminate to True, it at least makes your code less understandable).
A more important situation with terminating work threads is that you are trying to close an application while work thread is in wait state. The thread will not be awaken if you just call Terminate, generally you should use additional syncronization object (usually event) to wake up the work thread.
And one more remark - there is no need for
begin
MyThread.Terminate;
MyThread.WaitFor;
MyThread.Free;
end;
if you look at TThread.Destroy code, it calls Terminate and WaitFor, so
MyThread.Free;
is enough (at least in Delphi 2009, have no Delphi 7 sources at hand to check).
Updated
Read mghie answer. Consider the following situation (better on 1 CPU system):
main thread is executing
procedure TMainForm.Close;
begin
if not MyThreadReady then
begin
MyThread.Terminate;
MyThread.WaitFor;
MyThread.Free;
end;
end;
it checked MyThreadReady value (it is False) and was switched off by scheduler.
Now scheduler switches to work thread; it executes
Synchronize(ThreadFinished);
and forces scheduler to switch back to main thread. Main thread continues execution:
MyThread.Terminate; // no problem
MyThread.WaitFor; // ???
MyThread.Free;
can you say what will happen at WaitFor? I can't (requires a deeper look into TThread sources to answer, but at first glance looks like a deadlock).
Your real error is something different - you have written an unreliable code and trying to find out is it correct or not. That is bad practice with threads - you should learn to write a reliable code instead.
As for resources - when the TThread (with FreeOnTerminate = False) is terminated the only resources that remains allocated is Windows thread handle (it does not use substantial Windows resources after thread is terminated) and Delphi TThread object in memory. Not a big cost to be on the safe side.
Honestly, your
... Do some processing
Is the real problem here. Is that a loop for doing something recursively? If not and, instead, thats a huge task, you should consider split this task in small procedures / functions, and put all together in the execute body, calling one after another with conditional if's to know the thread state, like:
While not Terminated do
begin
if MyThreadReady then
DoStepOneToTaskCompletion
else
clean_and_or_rollback(Something Initialized?);
if MyThreadReady then
DoStepTwoToTaskCompletion
else
clean_and_or_rollback(Something Initialized?, StepOne);
if MyThreadReady then
DoStepThreeToTaskCompletion
else
clean_and_or_rollback(Something Initialized?, StepOne, StepTwo);
Self.DoTerminate; // Not sure what to expect from that one
end;
It is dirty, almost a hack, but will work as expected.
About FreeOnTerminate, well... just remove the declaration and always
FreeAndNil(ThreadObject);
I'm not a fan of syncronise. I like more critical sections, for the flexibility to extend the code to handle more shared data.
On the form public section, declare:
ControlSection : TRTLCriticalSection;
On form create or somewhere else before thread.create ,
InitializeCriticalSection(ControlSection);
Then, every time you write to a shared resource (including your MyThreadReady variable), do
EnterCriticalSection ( ControlSection );
MyThreadReady := True; //or false, or whatever else
LeaveCriticalSection ( ControlSection );
Before you go (exit), call
DeleteCriticalSection ( ControlSection );
and free your thread as you always do.
Regards
Rafael
I would state that mixing models is simply not recommended. You either use FreeOnTerminate and never touch the thread again, or you don't. Otherwise, you need a protected way for the two to communicate.
Since you want fine control over the thread variable, then don't use FreeOnTerminate. If your thread finishes early, clear the local resources that the thread has consumed as you normally would, and then simply let the main thread free the child thread when the application is finished. You'll get the best of both worlds - resources freed by the child thread as soon as it can be, and no worries about thread synchronization. (And it's got the added bonus of being much simpler in design/code/understanding/support...)

Resources