Recent

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

Slawek

  • New Member
  • *
  • Posts: 27
TThread - How to stop a thread?
« on: February 06, 2023, 01:36:49 am »
Hello,
How do I properly stop a thread from this example?
https://wiki.freepascal.org/Multithreaded_Application_Tutorial#The_TThread_Class
I have something like this:
Code: Pascal  [Select][+][-]
  1. procedure TMyThread.Execute;
  2. var newStatus : string;
  3.   i: Integer;
  4.   Fin: Boolean;
  5. begin
  6.   fStatusText := 'TMyThread Starting...';
  7.   Synchronize(@ShowStatus);
  8.   fStatusText := 'TMyThread Running...';
  9.   Fin:=False;
  10.   while (not Terminated) and (not Fin) do
  11.     begin
  12.       //...
  13.       for i:= 1 to 9 do begin
  14.         newStatus:='Task no '+i.ToString;
  15.         sleep(500);
  16.         //...
  17.         if newStatus <> fStatusText then begin
  18.           fStatusText := newStatus;
  19.           Synchronize(@Showstatus);
  20.         end;
  21.       end;
  22.       // Terminated := True; // ! But it's read-only !  :(
  23.       Fin := True;
  24.     end;
  25. end;
This is probably trivial, but I've never worked with threads before and have no experience.
Would it be correct to use Fin to stop this thread? (row 23)

And how to stop this thread from the outside, e.g. with the Stop button? (if there was no "Fin")
i.e. without "Fin" the task loop will repeat indefinitely. How to stop her?
What method sets the "Terminated" property to True?

Is that correct?
Code: Pascal  [Select][+][-]
  1. procedure TForm1.btStopClick(Sender: TObject);
  2. begin
  3.   MyThread.Terminate; //?
  4. end;

Please help.

Fred vS

  • Hero Member
  • *****
  • Posts: 2984
    • StrumPract is the musicians best friend
Re: TThread - How to stop a thread?
« Reply #1 on: February 06, 2023, 01:59:01 am »
Is that correct?
Code: Pascal  [Select][+][-]
  1. procedure TForm1.btStopClick(Sender: TObject);
  2. begin
  3.   MyThread.Terminate; //?
  4. end;

Yes.

If you want to only pause the thread you may use a RTLEvent.

Example:

Code: Pascal  [Select][+][-]
  1. var  // global variable
  2. evPause: PRTLEvent; // the pointer to a RTLEvent
  3. ...
  4. procedure TMyThread.Execute; // at begin of TMyThread.Execute
  5. ...
  6.   begin
  7.       evPause := RTLEventCreate;  // create the RTLEvent
  8. ...
  9.   end;
  10.  
  11. procedure TForm1.btPauseClick(Sender: TObject);
  12.   begin
  13.    if assigned(evPause) then
  14.     begin
  15.       RTLeventWaitFor(evPause); // wait for event
  16.       RTLeventResetEvent(evPause); // to pause the thread
  17.     end;  
  18.    end;
  19.  
  20. procedure TForm1.btResumeClick(Sender: TObject);
  21.   begin
  22.       if assigned(evPause) then
  23.       RTLeventSetEvent(evPause); // to resume the thread
  24.   end;
  25.  
  26. procedure TForm1.btStopClick(Sender: TObject);
  27.    begin
  28.     if assigned(MyThread) then
  29.       begin
  30.        MyThread.Terminate; // if FreeOnTerminate = True else add 'MyThread.free;'
  31.        MyThread.WaitFor;
  32.        if assigned(evPause) then
  33.        RTLeventdestroy(evPause);
  34.      end;
  35.   end;
  36.  
« Last Edit: February 06, 2023, 03:15:43 pm by Fred vS »
I use Lazarus 2.2.0 32/64 and FPC 3.2.2 32/64 on Debian 11 64 bit, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt.

https://github.com/fredvs
https://gitlab.com/fredvs
https://codeberg.org/fredvs

Thaddy

  • Hero Member
  • *****
  • Posts: 12933
Re: TThread - How to stop a thread?
« Reply #2 on: February 06, 2023, 02:39:06 am »
Is that correct?
Code: Pascal  [Select][+][-]
  1. procedure TForm1.btStopClick(Sender: TObject);
  2. begin
  3.   MyThread.Terminate; //?
  4. end;

Please help.

Yes.
No.
You forgot MyThread.WaitFor.
In memory of Gordon Moore  (January 3, 1929 – March 24, 2023) Just double the heaven every two years from now.

Fred vS

  • Hero Member
  • *****
  • Posts: 2984
    • StrumPract is the musicians best friend
Re: TThread - How to stop a thread?
« Reply #3 on: February 06, 2023, 02:48:38 am »
Is that correct?
Code: Pascal  [Select][+][-]
  1. procedure TForm1.btStopClick(Sender: TObject);
  2. begin
  3.   MyThread.Terminate; //?
  4. end;

Please help.

Yes.
No.
You forgot MyThread.WaitFor.

Indeed and I forgot RTLeventWaitFor(evPause) for pausing thread (fixed just now).
I use Lazarus 2.2.0 32/64 and FPC 3.2.2 32/64 on Debian 11 64 bit, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt.

https://github.com/fredvs
https://gitlab.com/fredvs
https://codeberg.org/fredvs

Roland57

  • Sr. Member
  • ****
  • Posts: 369
    • GitLab
Re: TThread - How to stop a thread?
« Reply #4 on: February 06, 2023, 07:09:27 am »
@Fred

I believe WaitFor should come after Terminate.

Terminate sets the Terminated variable to TRUE, and WaitFor waits until the thread execution is actually terminated.

@Slawek

If you wish your for loop to be executed only one time, you can simply remove the while condition.
« Last Edit: February 06, 2023, 07:18:46 am by Roland57 »

TRon

  • Hero Member
  • *****
  • Posts: 834
Re: TThread - How to stop a thread?
« Reply #5 on: February 06, 2023, 07:18:40 am »
@roland:
Well, you could do it the other way around as well. It just takes a little bit longer  :D

But yeah:
Terminate : https://www.freepascal.org/docs-html/rtl/classes/tthread.terminate.html
Quote
Signals the thread it should terminate.

Waitfor: https://www.freepascal.org/docs-html/rtl/classes/tthread.waitfor.html
Quote
Waits for the thread to terminate and returns the exit status.

GetMem

  • Hero Member
  • *****
  • Posts: 4026
Re: TThread - How to stop a thread?
« Reply #6 on: February 06, 2023, 08:38:39 am »
@Slawek
You cannot combine your "FIN" method with MyThread.Terminate it can lead to exception. When you set Fin to true, the thread's Execute method is exited and the thread terminated. At the time you call MyThread.Terminate, the thread is long gone.
A better solution is to check the Terminated flag before you do a long operation inside the thread, if Terminated is true, break the for cycle and exit the thread. On the other hand if the thread has finished all tasks, and terminated is still false, put the thread to idle and wait until MyThread.Terminated is called from the main thread. Something like this:
Code: Pascal  [Select][+][-]
  1. procedure TMyThread.Execute;
  2. var
  3.   I: Integer;
  4. begin
  5.   //...
  6.   for I := 1 to 9 do
  7.   begin
  8.     if Terminated then
  9.       Break; //abort
  10.     newStatus := 'Task no ' + I.ToString;
  11.     Sleep(500);
  12.     if newStatus <> fStatusText then
  13.     begin
  14.       fStatusText := newStatus;
  15.       if not Terminated then //only syncrhonize if terminated is false
  16.         Synchronize(@Showstatus);
  17.     end;
  18.   end;
  19.   while (not Terminated) do
  20.     Sleep(100); //thread is idle, wait until MyThread.Terminate is called
  21. end;

The idea is to create, terminate and free the worker thread(s) from the main thread. When you wish to free a particular thread:
Code: Pascal  [Select][+][-]
  1. MyThread.Terminate; //this will set the Terminated flag to true
  2. MyThread.WaitFor;
  3. MyThread.Free;
  4.  
WaitFor is critical. Not calling WaitFor can lead to deadlock(worker thread waits for the main thread, main thread waits for the worker thread...).

PS: If you have a list of mythreads, just before a particular thread goes to idle, set a public boolean variable Fin to true. From time to time loop through the list, check if a thread is idle(fin := true) then terminate/free the thread and remove it from the list.
« Last Edit: February 06, 2023, 08:40:55 am by GetMem »

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 811
Re: TThread - How to stop a thread?
« Reply #7 on: February 06, 2023, 08:45:10 am »
Proper way to terminate thread - to simply exit it's Execute method. Everything else is up to you. But there are some useful features. Terminated flag, that is set by Terminate method, is used as way to tell thread, that it should exit it's Execute method. You should check it periodically. WaitFor is used to wait for actual thread termination. Suspending thread is usually not good way to pause it. It's better to use some "soft" pause methods, such as sync objects, like events. Calling Sleep in cycle is easy but dirty way to pause thread. It still wastes some CPU cycles and causes delay between calling Terminate and actual thread termination.
« Last Edit: February 06, 2023, 08:47:08 am by Mr.Madguy »
29.12.2021 - migration to DynamicData 4.1 is completed - complete overhaul of data access driver.
My project still requires full Delphi 2009 support to be ported to Lazarus.
It's time to finally do it, because Delphi 2009 is 14 years old.

Fred vS

  • Hero Member
  • *****
  • Posts: 2984
    • StrumPract is the musicians best friend
Re: TThread - How to stop a thread?
« Reply #8 on: February 06, 2023, 03:06:15 pm »
@Fred

I believe WaitFor should come after Terminate.

Terminate sets the Terminated variable to TRUE, and WaitFor waits until the thread execution is actually terminated.

Salut Roland!

Thanks for the (tricky) trick.  :)

Fre;D
I use Lazarus 2.2.0 32/64 and FPC 3.2.2 32/64 on Debian 11 64 bit, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt.

https://github.com/fredvs
https://gitlab.com/fredvs
https://codeberg.org/fredvs

Warfley

  • Hero Member
  • *****
  • Posts: 1075
Re: TThread - How to stop a thread?
« Reply #9 on: February 06, 2023, 03:51:54 pm »
You should be careful with things like wait for as these can freeze up the thread. For example if a thread reads blocking IO it can take up to a few second timeout for the terms nated to be read, and wait for would block the whole waiting thread during this period.

It is usually better to use the onTerminated event, instead

Fred vS

  • Hero Member
  • *****
  • Posts: 2984
    • StrumPract is the musicians best friend
Re: TThread - How to stop a thread?
« Reply #10 on: February 06, 2023, 03:59:21 pm »
You should be careful with things like wait for as these can freeze up the thread. For example if a thread reads blocking IO it can take up to a few second timeout for the terms nated to be read, and wait for would block the whole waiting thread during this period.

It is usually better to use the onTerminated event, instead

Huh, I have to confess that I did not use WaitFor (before Thaddy remark) and never got problems using only Thread.terminate.
I use Lazarus 2.2.0 32/64 and FPC 3.2.2 32/64 on Debian 11 64 bit, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt.

https://github.com/fredvs
https://gitlab.com/fredvs
https://codeberg.org/fredvs

Warfley

  • Hero Member
  • *****
  • Posts: 1075
Re: TThread - How to stop a thread?
« Reply #11 on: February 06, 2023, 08:59:45 pm »
Terminate will let the thread stop (assuming it regularly checks for terminated), wait for or the on terminate event is if you want to kill it and then do something after said thread has been killed

Slawek

  • New Member
  • *
  • Posts: 27
Re: TThread - How to stop a thread?
« Reply #12 on: February 06, 2023, 09:27:28 pm »
Great!  :) Thank you all very much for these answers. Now I have to analyze and test them.
« Last Edit: February 07, 2023, 11:23:44 pm by Slawek »

Slawek

  • New Member
  • *
  • Posts: 27
Re: TThread - How to stop a thread?
« Reply #13 on: February 08, 2023, 02:22:46 pm »
When in btStartClick FreeOnTerminate := True;
Code: Pascal  [Select][+][-]
  1. procedure TForm1.btStartClick(Sender: TObject);
  2. begin
  3.   MyThread := TMyThread.Create(true);
  4.   MyThread.FreeOnTerminate := True;
  5.   MyThread.Start;
  6. end;

Then in btStopClick, after releasing the thread, I need to set the thread to nil otherwise I get an error when closing the form.
Code: Pascal  [Select][+][-]
  1. procedure TForm1.btStopClick(Sender: TObject);
  2. begin
  3.   MyThread.Terminate;
  4.   MyThread.WaitFor;
  5.   MyThread := nil;
  6. end;
  7.  
  8. procedure TForm1.FormDestroy(Sender: TObject);
  9. begin
  10.   if Assigned(MyThread) then begin
  11.     MyThread.Terminate;
  12.     MyThread.WaitFor;
  13.   end;
  14. end;
« Last Edit: February 08, 2023, 02:24:26 pm by Slawek »

alpine

  • Hero Member
  • *****
  • Posts: 660
Re: TThread - How to stop a thread?
« Reply #14 on: February 08, 2023, 04:37:19 pm »
*snip*
Then in btStopClick, after releasing the thread, I need to set the thread to nil otherwise I get an error when closing the form.
Code: Pascal  [Select][+][-]
  1. procedure TForm1.btStopClick(Sender: TObject);
  2. begin
  3.   MyThread.Terminate;
  4.   MyThread.WaitFor;
  5.   MyThread := nil;
  6. end;
  7.  
  8. procedure TForm1.FormDestroy(Sender: TObject);
  9. begin
  10.   if Assigned(MyThread) then begin
  11.     MyThread.Terminate;
  12.     MyThread.WaitFor;
  13.   end;
  14. end;
You should not use it that way in case FreeOnTerminate is True. Otherwise it may get freed between the Terminate and WaitFor and the latter call will be on invalid reference.
If you want the thread to free itself on termination, and there is some finalizing code to be executed, the proper way is to assign a handler to the OnTerminate event. That handler will be called with a (still) valid reference.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

 

TinyPortal © 2005-2018