?
Solved

TThread object still assigned after thread has terminated and been 'freed'.

Posted on 2007-10-16
10
Medium Priority
?
6,663 Views
Last Modified: 2008-03-26
Hello Experts,

I have an object of class TThread and I have set it up so that it should set itself to 'nil' after it has terminated. The pertinant code snippets are:-

  TFaxShotThread = class(TThread)
  private
    {...private fields/procedures...}
  protected
    procedure Execute; override;
    procedure genDealerFaxes();
    {...other procedures...}
  public
    destructor destroy(); override;
  end;

implementation

procedure TFaxShotThread.Execute;
begin
  Priority := tpIdle;
  FreeOnTerminate:=true;
  try
      while not Terminated do
         genDealerFaxes();
  except on E:Exception do begin
      {...handle Exception...}    
    end;
  end;
  free;
end;

procedure TFaxShotThread.genDealerFaxes();
begin
RunFaxShot(); // does the work
Terminate();
end;

destructor TFaxShotThread.destroy();
begin
{ no objects left to explicitly free so not much to do here }
inherited;
end;

It all seems very simple really.
'FreeOnTerminate' is set which means that the TThread should automatically call its '.free' method when it terminates, i.e. when control leaves the '.execute' method. This means that any variable of type TThread should have a value of 'nil' when execution has finished. Just for good measure the '.execute' method itself calls '.free' at the end.

It is when I use TFaxShotThread and examine the variable of this class in the debugger that I get the unexpected.

I use TFaxShotThread in another .pas unit, and, naturally enough, I have a variable of that class type and use '.create' to sart the thread running. Ths code goes like this:-

unit market

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, AdvPanel, ComCtrls, {......et al...}

type
  TmarketF = class(TForm)
    { lots of VCL components ...}
  private
   procedure commenceFaxShot();
   { more variables/procedures }
   public
    {.. and more variables/procedures }

var
  marketF     : TmarketF;

implementation

uses
  faxShotThread; { the unit containing the class TFaxShotThread }

var
  faxShotThread : TFaxShotThread;

procedure TMarketF.commenceFaxShot();
var
   ...
begin

{ check if 'faxShotThread' is still running. and if it is then tidy up...}  
         if Assigned(faxShotThread) then begin
            faxShotThread.Terminate();
            faxShotThread := nil;   // The only way to kill it off. This can't be right!
            end;
        { create a new instance of 'FaxShotThread' and run it...}
         faxShotThread := TfaxShotThread.create(false);

end;

The design philosophy is straightforward. The '.commenceFaxShot' method will be called multiple times during the lifetime of TMarketF and the seperate thread represented by the 'FaxShotThread' variable will be created and run from scratch each time. 'FaxshotThread' will terminate itself, 'free' itself, release all resources and become 'nil' when has it has finished executing.

Simple, eh?

Except that when I run the code under the debugger (Delphi 2006 on WinXP SP2) I see that 'FaxShotThread' is *not* set to 'nil' at the time of  the *second* call to 'commence'FaxShot' despite the vain attempts to use 'FreeOnTerminate' and 'free'.  The debugger 'hint' window dutifully shows the full structure of 'FaxShotThread' with the field names and values from its first life still there. And indeed, bizarrely, the debugger even tries to execute the 'FaxShotThread.terminate' method (the value for the pointer to it still exists) and then just hangs without raising any 'Access violation' error message! It seems that 'FreeOnTerminate' and 'free' do not work.

I thought that this surreal behaviour was down to some obscure debugger setting but the behaviour (i.e. hanging on executing 'FaxShotThread.terminate') was still the same when I ran the .exe.

Why doesn't the TThread object revert to a being a 'nil' value pointer when I have set 'FreeOnTerminate' to 'True'?

This is urgent and important, hence the big tariff, but points will go to those who display the best understanding as to what's going on here and how Delphi works.

Thank you in advance.

Stephen Farndon

(a) run for the first time or elsewill have only one possible state on only be exist in one of two states. Either it exists and it is running (not suspended) or it has a value of 'nil' meaning that it occupies no memory (any memory that it used to use should be put back in to circulation) and, by definition, is using no CPU time. The method '.commenceFaxShot' is only called in one of only two contexts. It is called when

The first time that '.commenceFaxShot' is called

So, if I run FaxShotThread by doing
0
Comment
Question by:SteveFarndon2000
9 Comments
 
LVL 19

Accepted Solution

by:
MerijnB earned 1000 total points
ID: 20089104
> This means that any variable of type TThread should have a value of 'nil' when execution has finished.

this is _not_ correct!

In Delphi, every object (and thus also a TThread) is actually a pointer. It points to the memory which is allocated for the object.
When you free the object, you tell the kernel that you don't need the memory anymore, but that does not mean the object (or actually the pointer) does not point to that memory anymore. You'll have to set the object to nil explicitly yourself after you've freed it (hence the function FreeAndNil())


> The debugger 'hint' window dutifully shows the full structure of 'FaxShotThread' with the field names and values from its first life still there. And indeed, bizarrely,
> the debugger even tries to execute the 'FaxShotThread.terminate' method (the value for the pointer to it still exists) and then just hangs without raising any
> 'Access violation' error message!

That you still see the 'full structure' is probably because the memory to which the object once pointed isn't re-used again, it still contains the data of the object.
I would expect an access violation, but when you are digging in memory which is not yours you never know what will happen.

>         if Assigned(faxShotThread) then begin
>            faxShotThread.Terminate();
>            faxShotThread := nil;   // The only way to kill it off. This can't be right!
>            end;

when setting faxShotThread to nil you are not killing it, all you are doing is remove your reference to the object, the object itself is still there. In this is it will free itself because you've set FreeOnTerminate to true.


As for a solution:

do you want to run multiple threads, if yes, do you really need to keep a pointer to all running thread, they will free themselves.
if no, make an event handler for the OnTerminate event, and set your reference (faxShotThread) to nil there yourself.
0
 
LVL 1

Expert Comment

by:Probie
ID: 20089478
Your design is bad if you need to check if the thread object is nil.
You should instead let your thread notify back to your main thread that it has finished (using SendMessage and some customized windows message) and from there do what you need to do.
0
 
LVL 19

Expert Comment

by:MerijnB
ID: 20091082
The thead has an OnTerminated event, which can be used for that, no need for SendMessage()
0
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 

Author Comment

by:SteveFarndon2000
ID: 20092329
MerijnB,

Yes, I was spectacularly wrong about  'free' and 'freeOnTerminate' setting the pointer to nil. I should have re-checked the Borland source.

I'm still not convinced about TThread.OnTerminate.

See this URL:-
http://members.aye.net/~bstowers/delphi/bugs/generated/documentation.htm

and look up bug number 447. The text is dated 1998 but the text that it quotes from Borland's Delphi Help is identical to Delphi 2006's help for TThread.OnTerminate. This bug report says and proves (see the example programme it references) that freeing a thread object (and therefore by implication assigning itself to nil as you suggest, I think) within the OnTerminate event handler is not always safe.

Moreover, Delphi 2006's Help for TThread.OnTerminate says that this event "Occurs after the thread's Execute method has returned and before the thread is destroyed". The way that I interpret this is that the sequence of control goes 'OnTerminate' then 'destroy' - at the risk of stating the blindingly obvious it would cause an 'Access violation' error if we had an 'FaxShotThread:=nil' statement in the 'OnTerminate' handler followed by a call to the thread objects 'destroy' procedure. Or am I missing something here...

For the points, you need to provide an example of how to use the TThread.OnTerminate event handler.

No, I don't want to run multiple threads. I simply want to make sure that there are no pointers left lying around causing memory leaks. At present I just have to trust that the Garbage Collector will come along after an object is 'freed' i.e. the object calls its own destructor method. At present I can't  'see' the Garbage Collector re-assigning the pointer memory, except by contriving some test code that runs many iterations and monitoring the process' memory use in Windows Task Manager perhaps. Setting a thread object pointer to nil identifies an instance of a thread object as finished with and, during development, will produce nice 'Access violation' exceptions instead of hanging our development machines.

Thanks for your contribution so far.

-------------------------------------------------------------------------------------------------------------
Probie,

Yes, I take your point. If you want points then let's see some example code, please.

Thanks for your contribution so far.

Stephen




0
 
LVL 19

Expert Comment

by:MerijnB
ID: 20092399
> I simply want to make sure that there are no pointers left lying around causing memory leaks.

In Delphi, pointers flying around (meaning they are not nil) do not cause memory leaks.

> At present I just have to trust that the Garbage Collector will come along after an object is 'freed' i.e. the object calls its own destructor method.

I would count on that, Delphi has no Garbage Collector :)

> Setting a thread object pointer to nil identifies an instance of a thread object as finished with

No, when you set the pointer to nil, you don't stop the thread from running, nor do you deallocate the memory used.

please explain: what should happen if you want to start a new thread, and you find out (one way or the other) that a previous thread is still running? Should the previous thread be killed, should you wait for it?
0
 
LVL 1

Assisted Solution

by:Probie
Probie earned 1000 total points
ID: 20093010
First of all declare a windows message to be used at the end of the execute method.
(CM_BASE is found in Controls.pas which needs to be in your uses clause)

const
 CM_THREAD_COMPLETED = CM_BASE + 1000;

TForm1 = class(TForm)
    procedure CMThreadCompleted(var Message: TMessage); message CM_THREAD_COMPLETED;
    procedure StartFaxShotThread;
// Do not decalre a Thread variable.. Let the thread float and create and start it from the StartFaxShotThread.
end;

procedure TForm1.CMThreadCompleted(var Message: TMessage); message CM_THREAD_COMPLETED;
begin
 // Thread has completed, do something in here...(you can add the object pointer in the WParam or LParam when sending the msaage if you want)
end;

procedure TForm1.StartFaxShotThread;
begin
 with TFaxShotThread.Create(true) do begin
  FormHandle := self.handle;
  FreeOnTerminate := true;
  resume;
 end;
end;

TFaxShotThread = class(TThread)
 FFormHandle : THandle;
...
...
       procedure Execute; override;
...
  property FormHandle : THandle read FFormHandle write FFormHandle;
end;

At the end of the Execute method you should add the SendMessage:

procedure TFaxShotThread.Execute;
begin
...
 SendMessage(FormHandle,integer(self),0));
 // You can replace 0 the last param (LParam) with some kind of result code if you want.
end;

This is a pretty simple code, and depending on what you do inside Thread.Execute you might want to add some ways to terminate the thread early in case your application is terminated etc.
0
 
LVL 1

Expert Comment

by:Probie
ID: 20093019
Sorry, I forgot to add the Message in the example..
It should be:

SendMessage(FormHandle,CM_THREAD_COMPLETED,integer(self),0));
0
 
LVL 19

Expert Comment

by:MerijnB
ID: 20093074
if you take a look at the implementation of TThread (classes.pas) you'll find that this does exactly the same as the OnTerminate event fired by TThread
0
 
LVL 1

Expert Comment

by:Computer101
ID: 21216690
Forced accept.

Computer101
EE Admin
0

Featured Post

Prep for the ITIL® Foundation Certification Exam

December’s Course of the Month is now available! Enroll to learn ITIL® Foundation best practices for delivering IT services effectively and efficiently.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

In this tutorial I will show you how to use the Windows Speech API in Delphi. I will only cover basic functions such as text to speech and controlling the speed of the speech. SAPI Installation First you need to install the SAPI type library, th…
Hello everybody This Article will show you how to validate number with TEdit control, What's the TEdit control? TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text. Usua…
Screencast - Getting to Know the Pipeline
With just a little bit of  SQL and VBA, many doors open to cool things like synchronize a list box to display data relevant to other information on a form.  If you have never written code or looked at an SQL statement before, no problem! ...  give i…
Suggested Courses

809 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question