Recent

Author Topic: TThread - How to stop a thread?  (Read 5127 times)

Slawek

  • New Member
  • *
  • Posts: 43
Re: TThread - How to stop a thread?
« Reply #15 on: February 08, 2023, 09:35:35 pm »
Thank you for your suggestion.
I did this:
Code: Pascal  [Select][+][-]
  1.   TForm1 = class(TForm)
  2.     ...
  3.   private
  4.     ...
  5.     procedure ThreadTerminated(Sender: TObject);
  6.   public
  7.  
  8.   end;
  9.  
  10. procedure TForm1.btStartClick(Sender: TObject);
  11. begin
  12.   ...
  13.   MyThread := TMyThread.Create(true);
  14.   MyThread.FreeOnTerminate := True;
  15.   ...
  16.   MyThread.OnTerminate  := @ThreadTerminated;
  17.   ...
  18.   MyThread.Start;
  19.   ...
  20. end;  
  21.  
  22. procedure TForm1.btStopClick(Sender: TObject);
  23. begin
  24.   MyThread.Terminate;
  25.   MyThread.WaitFor;
  26.   // MyThread := nil; //moved to ThreadTerminated
  27. end;
  28.  
  29. procedure TForm1.FormDestroy(Sender: TObject);
  30. begin
  31.   if Assigned(MyThread) then begin
  32.     MyThread.Terminate;
  33.     MyThread.WaitFor;
  34.   end;
  35. end;
  36.  
  37. procedure TForm1.ThreadTerminated(Sender: TObject);
  38. begin
  39.   MyThread := nil;
  40. end;
  41.  
Thank you. It seems to be working properly. Does not cause errors  :)


alpine

  • Hero Member
  • *****
  • Posts: 1410
Re: TThread - How to stop a thread?
« Reply #16 on: February 09, 2023, 10:04:17 am »
*snip*
Thank you. It seems to be working properly. Does not cause errors  :)
You didn't change it much.

Maybe I wasn't clear enough in the previous post and I need to explain a bit more.

There is an internal procedure named TreadProc which actually is the "main" tread procedure for each TThread.  It receives a reference to the TThread instance, internally calls the the Execute method for that instance, and when that method finishes, if the FreeOnTerminate property value is true, it calls Free method. So the steps performed by TreadProc are actually (assume the name of the reference is Thread):
  • If Thread still not terminated then call Thread.Execute (the thread can be terminated when initially suspended and the Terminate called prior to Start)
  • Set Thread.Finished to true
  • if there is a handler for Thread.OnTerminate then call it
  • if Thread.FreeOnTerminate is true then call Thread.Free
  • End.

Few things should be noted:
  • The TThread.Terminate method just sets the Thread.Terminated property to true. That is the property the Thread.Execute must examine and exit when it becomes true (in a graceful manner)
  • The Thread.Execute may finish prematurely by other means than the above Terminate mechanism, e.g. by unhandled exception. Thus, one should not expect the thread to be finished only after call to Thread.Terminate
  • The above (combined with FreeOnTerminate=True) means that the reference can be freed asynchronously at any time and once started, there is no more guarantees that its reference is a valid one (not freed)
  • At the other hand, the OnTerminate is called in the context of the thread itself, so there the reference is guaranteed to be valid (not yet freed)

To recap, when FreeOnTerminate=True then:
Code: Pascal  [Select][+][-]
  1.   MyThread.Terminate;
isn't safe to call, actually accessing the MyThread isn't safe at all unless you catch everything (try ... except ... end) into the inner loop of MyThread.Execute

Also, when FreeOnTerminate=True and after the:
Code: Pascal  [Select][+][-]
  1.   MyThread.Terminate;
isn't safe to assume anything about the MyThread reference outside the MyThread.Execute and MyThread.OnTerminate handler body.

Code: Pascal  [Select][+][-]
  1. procedure TForm1.btStopClick(Sender: TObject);
  2. begin
  3.   MyThread.Terminate; // No more asumptions about validity of MyThread after that point
  4.   MyThread.WaitFor; // <--- Here the ThreadProc(MyThread) may already have freed the MyThread reference !!!
  5.                     //      As a pure luck, the memory contents there may still hold the old values for MyThread
  6.   // MyThread := nil; //moved to ThreadTerminated
  7. end;

And that it doesn't cause errors now on your computer does not mean it won't make your life miserable later.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Sanos

  • Newbie
  • Posts: 1
Re: TThread - How to stop a thread?
« Reply #17 on: November 02, 2025, 10:59:16 pm »
Hi,

thanks to your inputs I have successfully solved this issue to forcefully terminate all threads and then being able to close the main form.

Code below, using a thread array:

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.  //
  9. type
  10.  
  11.   { TMyMThread }
  12.  
  13.   TMyMThread = class(TThread)
  14.       {custom procedures}
  15.     procedure DoM(ParamThreadNumber: integer);
  16.  
  17.   private
  18.     //other private variables
  19.     fForceTerminate: boolean;
  20.     procedure CopyImage;
  21.     procedure ShowStatusY;
  22.  
  23.   protected
  24.     procedure Execute; override;
  25.   public
  26.     constructor Create(CreateSuspended: boolean; ParamThreadNumber:integer);
  27.     procedure InitTerminate;
  28.     // other properties
  29.     property ForceTerminate:boolean  read fForceTerminate write fForceTerminate;
  30. end;
  31.  
  32.  
  33.   { TForm1 }
  34.  
  35.   TForm1 = class(TForm)
  36.   // classic variables and procedures
  37.  
  38.  
  39.   private
  40.   //other variables
  41.   MThreads : array of TMyMThread;
  42.  
  43.  
  44.   public
  45.  
  46.   end;
  47.  
  48. var
  49.   Form1: TForm1;
  50.  
  51. implementation
  52.  
  53. {$R *.lfm}
  54.  
  55. { TMyMbrotThread }
  56.  
  57. constructor TMyMThread.Create(CreateSuspended: boolean; ParamThreadNumber:integer);
  58. begin
  59.   Self.ThreadNumber:=ParamThreadNumber;
  60.   Self.ForceTerminate:=false;;
  61.   inherited Create(CreateSuspended);
  62. end;
  63.  
  64. procedure TMyMThread.Execute;
  65. begin
  66.   // init all thread specific properties
  67.   Self.FreeOnTerminate:=true;
  68.   Self.DoMB(Self.ThreadNumber); //Main worker procedure
  69.   Self.Terminate;
  70.   Self.Free;
  71. end;
  72.  
  73. procedure TMyMThread.InitTerminate;
  74. begin
  75.   //setting the thread flag, telling it must exit the procedure called by Execute
  76.   Self.ForceTerminate:=true;
  77. end;
  78.  
  79. procedure TMyMThread.CopyImage;
  80. // this method is only called by Synchronize(@ShowStatus)
  81. begin
  82.   //I am copying here a temp image
  83. end;
  84.  
  85. procedure TMyMThread.ShowStatusY;
  86. // this method is only called by Synchronize(@ShowStatus)
  87. var pos: int64;
  88. begin
  89.   //example of how to update a progressBar array to see the % of work performed by each thread on a separate progressbar
  90.   //iy is the Y worked by the thread, myiymax is the height of the image on the main form, passed as parameter to the thread
  91.   pos:=100*iy div Self.myiymax;
  92.   Form1.MProgress[Self.ThreadNumber].Position:=pos;
  93.   Form1.MProgress[Self.ThreadNumber].Hint:='Thread #'+IntToStr(Self.ThreadNumber)+ ' progress: '+pos.ToString+'%';
  94. end;
  95.  
  96. procedure TMyMThread.DoM(ParamThreadNumber: integer);
  97. const
  98.    //
  99. var
  100.    //
  101.  
  102. begin
  103.       MyImage:=TBitmap.Create;
  104.       for localiy := 1 to myiymax do
  105.       begin
  106.          // make computations for y
  107.          for localix := Self.myixmin to Self.myixmax do
  108.          begin
  109.             //check if one must terminate early
  110.             if Self.ForceTerminate then
  111.             begin
  112.               MyImage.Destroy;
  113.               Self.Terminate;
  114.               Exit;
  115.             end;
  116.             // make computations for x and draw pixels on the image
  117.             MyImage.Canvas.Pixels[ix-Self.myixmin,iy]:=$FFFFFF div maxiteration*iteration;
  118.             //MyImage.Canvas.Pixels[ix-myixmin,iy]:=$FFFFFF - iteration*1000;
  119.          end;
  120.                  //tell the main form how much work was done, sync for Y
  121.          Self.Synchronize(@ShowStatusY);
  122.       end;
  123.          
  124.       Self.Synchronize(@CopyImage);
  125.       MyImage.Destroy;
  126. end;
  127.  
  128. { TForm1 }
  129. //all other TForm1 procedures
  130.  
  131. procedure TForm1.BitBtn2Click(Sender: TObject);
  132. var
  133.   i:integer;
  134. begin
  135.       //clicking on the execution button leads to the creation of the threads
  136.       for i:=Low(MThreads) to High(MThreads)  do
  137.       begin
  138.          MThreads[i]:=TMyMThread.Create(False, i);
  139.                  //Starting the thread calls in fact its Execute method.
  140.                  //Thread properties should not be assigned values here, in the main thread
  141.                  //but in the thread's execute method instead
  142.          MThreads[i].Start;
  143.       end;
  144.  
  145. end;
  146.  
  147.  
  148. //FormCloseQuery asks if the form may be closed
  149. //without terminating all the threads while they were still working, the form did not close
  150. //so I had to kill by force all the threads, like this:
  151.  
  152.  
  153. procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  154. var
  155.   i:integer;
  156. begin
  157.      //send forceful termination flag
  158.      for i:=Low(JThreads) to High(JThreads)  do
  159.      begin
  160.          //Check Assigned to avoid setting a property for a thread that is nil
  161.        if Assigned(MThreads[i]) then
  162.           MThreads[i].InitTerminate;
  163.        Application.ProcessMessages;
  164.        // nil the thread
  165.        MThreads[i]:=nil;
  166.      end;
  167.          
  168.      // Wait for all threads to terminate, if they exist
  169.  
  170.      for i:=Low(JThreads) to High(JThreads)  do
  171.      begin
  172.        if Assigned(MThreads[i]) then MThreads[i].WaitFor;
  173.      end;
  174.  
  175.      //Tell the form it may close now
  176.      CanClose:=true;
  177.  end;
  178.  
  179. end.
  180.  
  181.  

Thaddy

  • Hero Member
  • *****
  • Posts: 18305
  • Here stood a man who saw the Elbe and jumped it.
Re: TThread - How to stop a thread?
« Reply #18 on: November 03, 2025, 09:51:21 am »
@Alpine
Thread.Waitfor is the correct way. The error is to use FreeOnTerminate, which you would almost never do.
Order is Terminate -> WaitFor-->Free. FreeOnTerminate is only useful for threads that need no further communication when done, e.g. in the context of a webserver.
If the blocking nature of waitfor is a limiting factor, you can use a controller thread that handles the creation, waifor and free and let the program wait on the controller thread at the end of the program. This keeps the program responsive.
« Last Edit: November 03, 2025, 09:58:51 am by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

alpine

  • Hero Member
  • *****
  • Posts: 1410
Re: TThread - How to stop a thread?
« Reply #19 on: November 04, 2025, 08:32:19 am »
@Alpine
Thread.Waitfor is the correct way. The error is to use FreeOnTerminate, which you would almost never do.
Isn't that what I'm saying too?
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

cdbc

  • Hero Member
  • *****
  • Posts: 2462
    • http://www.cdbc.dk
Re: TThread - How to stop a thread?
« Reply #20 on: November 04, 2025, 08:54:11 am »
Hi
Hmmm, I dunno if it's off-topic, but there's also this way to do it:
Code: Pascal  [Select][+][-]
  1. type
  2.   { TMyMThread }
  3.   TMyMThread = class(TThread)
  4.       {custom procedures}
  5.     procedure DoM(ParamThreadNumber: integer);
  6.   private
  7.     //other private variables
  8.     fForceTerminate: boolean;
  9.     procedure CopyImage;
  10.     procedure ShowStatusY;
  11.   protected
  12.     procedure Execute; override;
  13.   public
  14.     constructor Create(CreateSuspended: boolean; ParamThreadNumber:integer);
  15.     procedure BeforeDestruction;override; ///<---HERE
  16.     procedure InitTerminate;
  17.     // other properties
  18.     property ForceTerminate:boolean  read fForceTerminate write fForceTerminate;
  19. end;
  20. ...
  21. procedure TMyMThread.BeforeDestruction; ///<---HERE
  22. begin
  23.   Terminate;
  24.   WaitFor;
  25.   inherited BeforeDestruction;
  26. end;
The above allows you to just free the thread... 8-)
Just my 2 cent's worth
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6 -> FPC 3.2.2 -> Lazarus 4.0 up until Jan 2025 from then on it's both above &: KDE6/QT6 -> FPC 3.3.1 -> Lazarus 4.99

alpine

  • Hero Member
  • *****
  • Posts: 1410
Re: TThread - How to stop a thread?
« Reply #21 on: November 04, 2025, 09:50:44 am »
@cdbc
BeforeDestruction called in the (same) thread context, so WaitFor does actually nothing. Can't make sense of it.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

cdbc

  • Hero Member
  • *****
  • Posts: 2462
    • http://www.cdbc.dk
Re: TThread - How to stop a thread?
« Reply #22 on: November 04, 2025, 10:32:51 am »
Hi alpine
You need to study TThread a bit harder............ :P
I'll leave you with a quick example -- watch the ThreadID  8-)
Code: Pascal  [Select][+][-]
  1. program testthread;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses {$IFDEF UNIX} cthreads, {$ENDIF} Classes, sysutils;
  6.  
  7. type
  8.  
  9.   { TTestThread }
  10.  
  11.   TTestThread = class(TThread)
  12.   protected
  13.     procedure Execute; override;
  14.   public
  15.     constructor Create({%H-}CreateSuspended: Boolean; const {%H-}StackSize: SizeUInt= DefaultStackSize);
  16.     procedure BeforeDestruction; override;
  17.   end;
  18.  
  19. var
  20.   tst: TTestThread;
  21.  
  22. { TTestThread }
  23. procedure TTestThread.Execute;
  24. var li: integer = 0;
  25. begin
  26.   writeln('(i) Execute begin -> ThreadID: ',ThreadID);
  27.   while ((not Terminated) and (li < 10)) do begin
  28.     write('#');
  29.     sleep(500);
  30.     inc(li);
  31.   end;                            
  32.   writeln(LineEnding,'(i) Execute end   -> ThreadID: ',GetCurrentThreadId);
  33.   writeln('Push [Enter]');
  34. end;
  35.  
  36. constructor TTestThread.Create(CreateSuspended: Boolean;
  37.                                const StackSize: SizeUInt);
  38. begin
  39.   inherited Create(true);
  40.   writeln('(i) Create -> ThreadID: ',GetCurrentThreadId); /// CurrentThread.ThreadID
  41.   Start;
  42. end;
  43.  
  44. procedure TTestThread.BeforeDestruction;
  45. begin
  46.   Terminate;
  47.   WaitFor;
  48.   writeln('(i) BeforeDestruction -> ThreadID: ',GetCurrentThreadId);
  49.   inherited BeforeDestruction;
  50. end;
  51.  
  52. begin
  53.   writeln('  Thread Test start - ID = ',GetCurrentThreadId);
  54.   tst:= TTestThread.Create(false);
  55.   readln;
  56.   tst.Free;                                      
  57.   writeln('  Thread Test done  - ID = ',GetCurrentThreadId);
  58. end.
  59.  
A small CLI-app to demonstrate, I dunno what /ifdef's/ it'll take to run in winders  :D
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6 -> FPC 3.2.2 -> Lazarus 4.0 up until Jan 2025 from then on it's both above &: KDE6/QT6 -> FPC 3.3.1 -> Lazarus 4.99

Thaddy

  • Hero Member
  • *****
  • Posts: 18305
  • Here stood a man who saw the Elbe and jumped it.
Re: TThread - How to stop a thread?
« Reply #23 on: November 04, 2025, 11:08:31 am »
Benny, Alpine is right: do not call waitfor in the context of the thread itself. Waitfor is for the other contexts, the lookers/observers.
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

alpine

  • Hero Member
  • *****
  • Posts: 1410
Re: TThread - How to stop a thread?
« Reply #24 on: November 04, 2025, 11:09:57 am »
@cdbc
Sorry, my bad, I was fixated on the context of FreeOnTerminate:=True.
Last one is perfectly fine.

IMO I don't see the reason to override the method and hide it instead of:
Code: Pascal  [Select][+][-]
  1.   tst.Terminate;
  2.   tst.WaitFor;
  3.   tst.Free;
It seems a bit more compelling. 8)
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Thaddy

  • Hero Member
  • *****
  • Posts: 18305
  • Here stood a man who saw the Elbe and jumped it.
Re: TThread - How to stop a thread?
« Reply #25 on: November 04, 2025, 11:20:40 am »
I think your first hunch was correct: It makes no sense to wait for one self.
If my wife would wait for herself that would be infinity.
On the other hand, some accuse me of patience.

The wait functions are not for the thread itself.
« Last Edit: November 04, 2025, 11:22:24 am by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

cdbc

  • Hero Member
  • *****
  • Posts: 2462
    • http://www.cdbc.dk
Re: TThread - How to stop a thread?
« Reply #26 on: November 04, 2025, 11:27:50 am »
Hi
Ok, I'll spell it out then...:
'BeforeDestruction' gets called in the 'MAIN-Thread' context, as I clearly demonstrated!
<Important>Only 'Execute' runs in the thread context!!!</Important>
Look at how TThread is implemented...
Sven/Sarah is pretty clever  :D
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6 -> FPC 3.2.2 -> Lazarus 4.0 up until Jan 2025 from then on it's both above &: KDE6/QT6 -> FPC 3.3.1 -> Lazarus 4.99

Thaddy

  • Hero Member
  • *****
  • Posts: 18305
  • Here stood a man who saw the Elbe and jumped it.
Re: TThread - How to stop a thread?
« Reply #27 on: November 04, 2025, 01:36:03 pm »
The logic escapes me, but I will test what actually happens.
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

 

TinyPortal © 2005-2018