Forum > General

TThread in an array - freezes on closing

(1/2) > >>

Slawek:
The next problem with threads I see is that I wanted to create an array of threads. Run them all together and finish with the button or close the form.
When the array is a single element: array[0..0], everything works. However with 2 elements: array[0..1] everything hangs when I click btStop or close the form. I don't understand why the program crashes.

Second case. I send ThreadID and GetCurrentThreadId to ListBox(lbThreads). ThreadID is different for each thread while GetCurrentThreadId is the same. Does GetCurrentThreadId show the id of the main thread in this situation?

The whole code looks like this:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ComCtrls; type  TShowStatusEvent = procedure(Id, ThId: Integer; Status, Task: String) of Object;   { TMyThread }   TMyThread = class(TThread)  private    FId: Integer;    fStatusText : string;    fTaskText : string;    FOnShowStatus: TShowStatusEvent;    procedure SetId(AValue: Integer);    procedure ShowStatus;  protected    procedure Execute; override;  public    constructor Create(CreateSuspended : boolean);    property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;    property Id: Integer read FId write SetId;  end;   { TForm1 }   TForm1 = class(TForm)    btStart: TButton;    btStop: TButton;    laTask: TLabel;    laStatus: TLabel;    lbThreads: TListBox;    StatusBar1: TStatusBar;    procedure btStartClick(Sender: TObject);    procedure btStopClick(Sender: TObject);    procedure FormCreate(Sender: TObject);    procedure FormDestroy(Sender: TObject);  private    procedure ShowStatus(Id, ThId: Integer; Status, Task: string);    procedure ThreadTerminated(Sender: TObject);  public   end; var  Form1: TForm1;  aThreads: array[0..1] of TMyThread; implementation {$R *.lfm} { TMyThread } procedure TMyThread.ShowStatus;begin  if Assigned(FOnShowStatus) then begin    FOnShowStatus(FId, ThreadID, fStatusText, fTaskText);  end;end; procedure TMyThread.SetId(AValue: Integer);begin  if FId=AValue then Exit;  FId:=AValue;end; procedure TMyThread.Execute;var i: Integer;begin  fStatusText := 'Starting...';  Synchronize(@ShowStatus);  fStatusText := 'Running...';  while (not Terminated) do begin //repeat all tasks    for i:= 1 to 9 do begin      if Terminated then Break; //abort      fTaskText:='Task no '+i.ToString;      sleep(500);      if not Terminated then        Synchronize(@Showstatus);    end;  end;end; constructor TMyThread.Create(CreateSuspended: boolean);begin  inherited Create(CreateSuspended);end; { TForm1 } procedure TForm1.btStartClick(Sender: TObject);var i: Integer;begin  btStart.Enabled:=False;  lbThreads.Clear;  for i:= Low(aThreads) to High(aThreads) do    lbThreads.Items.Add('.');   for i:= Low(aThreads) to High(aThreads) do begin    aThreads[i] := TMyThread.Create(true);    aThreads[i].FreeOnTerminate := True;    aThreads[i].OnShowStatus := @ShowStatus;    aThreads[i].OnTerminate  := @ThreadTerminated;    aThreads[i].Id:=i;    laStatus.Caption:='Started';    aThreads[i].Start;  end;  btStop.Enabled:=True;end; procedure TForm1.btStopClick(Sender: TObject);var i: Integer;begin  btStop.Enabled:=False;  for i:= Low(aThreads) to High(aThreads) do begin    aThreads[i].Terminate;    aThreads[i].WaitFor;  end;  laStatus.Caption:='Terminated';  btStart.Enabled:=True;  if Assigned(aThreads[0]) then ShowMessage('Assigned!');end; procedure TForm1.FormCreate(Sender: TObject);begin  btStop.Enabled:=False;end; procedure TForm1.FormDestroy(Sender: TObject);var i: Integer;begin  for i:= Low(aThreads) to High(aThreads) do begin    if Assigned(aThreads[i]) then begin      aThreads[i].Terminate;      aThreads[i].WaitFor;    end;  end;end; procedure TForm1.ShowStatus(Id, ThId: Integer; Status, Task: string);const TXT='%S(%D/%D/%D)=%S';var cThId: Integer;begin  laStatus.Caption := Status;  laTask.Caption := Task;  cThId := GetCurrentThreadId;  lbThreads.Items[Id] := Format(TXT, [Status, Id, ThId, cThId, Task]);end; procedure TForm1.ThreadTerminated(Sender: TObject);begin  if Sender is TMyThread then aThreads[(Sender as TMyThread).Id] := nil;end; end. 

jamie:
if you do a Waitfor on a thread that never got started or was placed in suspend mode it may hang there at the Waitfor.

 Don't use Waitfor unless you know the thread is actually running.

This could be one of those little bugs we don't like to talk about.! Oh No, Say it's not so!

Slawek:
Hello jamie,


--- Quote from: jamie on February 09, 2023, 02:36:07 am ---if you do a Waitfor on a thread that never got started or was placed in suspend mode it may hang there at the Waitfor.

 Don't use Waitfor unless you know the thread is actually running.

--- End quote ---
Thank you for your comments.
I deleted both Waitfors and now there is indeed no freeze.
1.
But shouldn't I wait for all threads to finish when closing the application and/or form?
2.
I don't understand this. If I tell you to terminate threads (aThreads.Terminate), then why aThreads.WaitFor; freezes? So how can I make sure threads are actually terminated here?


--- Quote from: jamie on February 09, 2023, 02:36:07 am ---This could be one of those little bugs we don't like to talk about.! Oh No, Say it's not so!

--- End quote ---
I like to talk about all the bugs, because only then will I be able to write correct code.
It annoys me when I don't understand something and make stupid mistakes.

Warfley:
For this purpose there exist a special synchronization datastructure called a barrier: https://en.m.wikipedia.org/wiki/Barrier_(computer_science)
 At its core it works like this, a barrier requires a certain number of threads to break through. A thread that runs into the barrier waits until enough threads are waiting and then all of them will continue at once.

I always think of it as a race track, where everyone waits at the start line until all are in place and then the race starts.

In your program you can use a barrier to wait for the threads. When starting the threads you setup the barrier with a threshold of n+1 (where n is the number of threads). In the on terminate of your threads you let them enter the barrier. When waiting for all threads your main thread enters the barrier as well. As soon as all the threads are finished, and the main thread is also at the point of entering the barrier, the barrier releases, all the threads that are waiting will continue with their FreeOnTerminate, and the main thread will continue execution knowing that all other threads just finished

alpine:
@Slawek
You are making the same mistake I wrote about:
https://forum.lazarus.freepascal.org/index.php/topic,62182.msg470478.html#msg470478

Just comment the:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---    aThreads[i].FreeOnTerminate := True; in the btStartClick and you'll see the difference.

You're calling WaitFor on a dangling reference!

Navigation

[0] Message Index

[#] Next page

Go to full version