Jump to content
pcplayer99

TThread issue

Recommended Posts

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
  FMX.Controls.Presentation;

type
  TForm1 = class(TForm)
    ToolBar1: TToolBar;
    Button1: TSpeedButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    FNumb: Integer;
  public
    { Public declarations }
  end;

  TMyThread = class(TThread)
  private
    procedure aaaa;
  public
    procedure Execute; override;
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

{ TMyThread }

procedure TMyThread.aaaa;
begin

  Form1.Button1.Text := 'aaa' + Form1.FNumb.ToString;
  Form1.Updated;

  Inc(Form1.FNumb);
end;

procedure TMyThread.Execute;
begin
  inherited;
  Self.Synchronize(aaaa);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  T: TMyThread;
begin

  T := TMyThread.Create(True);

  T.Start;
end;

end.

 

 

Above code can run OK under Windows, but in Android, it can not run into TMyThread.Execute;

 

if Add a Sleep(500) as code below, it can run OK in Android:

 

procedure TForm1.Button1Click(Sender: TObject);
var
  T: TMyThread;
begin

  T := TMyThread.Create(True);
  Sleep(500);
  T.Start;
end;

 

So, I guess in Android, var T is a local variable and after exit Button1Click method, it was released by ARC.

 

So I move var T: TMyThread declare to global variable at var Form1: TForm, and then delete Sleep(500) and then tested in Android, it works OK.

Share this post


Link to post

  T := TMyThread.Create(True);

  T.Start;

 

equal:

 

T := TMyThread.Create;

 

After  T := TMyThread.Create this thread run immediately.

 

I write this:

  T := TMyThread.Create(True);

  T.Start;

 

just want to place a break point here when thread was created and see what happend. And I have seen something: when there is a break point, and program stopped here, and then I press F8, and F8, it works fine. So I guess that maybe I can stop here for some while and I add a Sleep(500) here and it works fine.

Edited by pcplayer99

Share this post


Link to post
7 hours ago, pcplayer99 said:

So, I guess in Android, var T is a local variable and after exit Button1Click method, it was released by ARC.

 

So I move var T: TMyThread declare to global variable at var Form1: TForm, and then delete Sleep(500) and then tested in Android, it works OK.

However I have 0 experience in cross-platform, this sounds perfectly reasonable. My 2 cents are that if any object (TMyThread) need to live and be accessible throughout a method, define it's variable in the parent (TForm1), worst-case-scenario unit global. It makes sense to me like this and it seems the garbage collector thinks the same way.

 

@David Heffernan

My guess is that there is a reason for suspended creation, it is just not included in the example code above. What I try to say is, I always create TThreads suspended myself but mostly because I want to do stuff with them before the actual execution (e.g. .NameThreadForDebugging, etc.).

Share this post


Link to post
3 hours ago, David Heffernan said:

What happens if you pass False and so don't create suspended? 

I know that this isn't really what you're asking but I don't think I've ever had a case where one didn't need to create suspended.

 

You always have to either pass some information to the thread or initialize it's data and if you don't create it suspended there will be a race between the creator and the thread for access to the thread data structures. For example:

type
  TMyThread = class(TThread)
  private
    FData: TSomeType;
  protected
    procedure Execute; override;
  public
    construtor Create(const AValue: TSomeType);
  end;

construtor TMyThread.Create(const AValue: TSomeType);
begin
  inherited Create(False); // Thread starts immediately
  FData := AValue; // Race condition here
end;

procedure TMyThread.Execute;
begin
  FData.WhatEver; // Race condition here
end;

var
  Thread: TMyThread;
begin
  Thread := TMyThread.Create(SomeData);
end;

 

Share this post


Link to post
13 minutes ago, Anders Melander said:

I know that this isn't really what you're asking but I don't think I've ever had a case where one didn't need to create suspended.

 

You always have to either pass some information to the thread or initialize it's data and if you don't create it suspended there will be a race between the creator and the thread for access to the thread data structures. For example:


type
  TMyThread = class(TThread)
  private
    FData: TSomeType;
  protected
    procedure Execute; override;
  public
    construtor Create(const AValue: TSomeType);
  end;

construtor TMyThread.Create(const AValue: TSomeType);
begin
  inherited Create(False); // Thread starts immediately
  FData := AValue; // Race condition here
end;

procedure TMyThread.Execute;
begin
  FData.WhatEver; // Race condition here
end;

var
  Thread: TMyThread;
begin
  Thread := TMyThread.Create(SomeData);
end;

 

Actually, there is no race condition because thread is actually started in AfterConstruction. So you can put just about anything you need in thread constructor and create thread in non suspended state.

  • Like 1
  • Thanks 1

Share this post


Link to post
19 minutes ago, Anders Melander said:

I know that this isn't really what you're asking but I don't think I've ever had a case where one didn't need to create suspended.

 

You always have to either pass some information to the thread or initialize it's data and if you don't create it suspended there will be a race between the creator and the thread for access to the thread data structures.

That was a bug fixed in Delphi 6, and one of the reasons for the introduction of AfterCreation. I never create threads suspended.

  • Thanks 1

Share this post


Link to post
1 minute ago, Dalija Prasnikar said:

Actually, there is no race condition because thread is actually started in AfterConstruction. So you can put just about anything you need in thread constructor and create thread in non suspended state.

Oh... You're right. It's even documented:

Quote

By calling Execute from the AfterConstruction method rather than the constructor, TThread avoids a race condition where the thread may free itself after Execute finishes but before the main thread calls AfterConstruction.

I think I'll continue to use the create-suspended pattern anyway since it explicitly does what I wish.

Share this post


Link to post
37 minutes ago, aehimself said:

My guess is that there is a reason for suspended creation, it is just not included in the example code above. What I try to say is, I always create TThreads suspended myself but mostly because I want to do stuff with them before the actual execution (e.g. .NameThreadForDebugging, etc.).

Do that in the constructor of the thread if you wish. No need to create the thread suspended.

Share this post


Link to post
Just now, Anders Melander said:

I think I'll continue to use the create-suspended pattern anyway since it explicitly does what I wish.

Creating them non-suspended explicitly does what you wish also.

Share this post


Link to post
1 minute ago, David Heffernan said:

That was a bug fixed in Delphi 6, and one of the reasons for the introduction of AfterCreation.

That must have been around the time when I stopped reading the documentation 🙂

I can't see why it would be classed as a bug though.

Share this post


Link to post
2 minutes ago, Anders Melander said:

That must have been around the time when I stopped reading the documentation 🙂

I can't see why it would be classed as a bug though.

It's a bug for the very reasons that you raised! When the thread was created in the constructor of `TThread` it meant that you couldn't synchronise passing state to the thread before it started.

  • Like 1

Share this post


Link to post
1 minute ago, David Heffernan said:

It's a bug for the very reasons that you raised!

I guess it's a matter of perspective.

If you didn't expect the thread to start until after the constructor then I can see how the behavior would be seen as a bug. I never expected it to behave any other way so to me it was just an annoyance.

 

A bit like FreeOnTerminate. A pretty good way to shoot one self in the foot.

Share this post


Link to post
14 hours ago, pcplayer99 said:

So, I guess in Android, var T is a local variable and after exit Button1Click method, it was released by ARC.

TThread holds an internal reference to itself (in the TThread.CurrentThread property), which under ARC ensures the thread stays alive and continues to run as expected even if all other references to the TThread object get cleared.  That internal reference gets cleared when the thread stops running.

 

However, that internal reference does not get assigned until the thread is actually running, which is why you need the extra delay on ARC systems, otherwise if ARC calls the TThread destructor, it will terminate the newly-created thread before it begins to run.

 

Rather than using an arbitrary sleep, you should instead use a waitable event, like TEvent.  Have the code that creates the TThread object wait on that event before continuing.  Signal the event at the beginning of your thread's Execute().  For example:

type
  TMyThread = class(TThread)
  private
    procedure aaaa;
    FStarted: TEvent;
  protected
    procedure Execute; override;
  public
    constructor Create; reintroduce;
    destructor Destroy; override;
    procedure WaitForStart;
  end;

constructor TMyThread.Create;
begin
  inherited Create(False);
  FStarted := TEvent.Create;
end;

destructor TMyThread.Destroy;
begin
  FStarted.Free;
  inherited;
end;

procedure TMyThread.Execute;
begin
  FStarted.SetEvent;
  ...
end;

procedure TMyThread.WaitForStart;
begin
  FStarted.WaitFor(Infinite);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  T: TMyThread;
begin
  T := TMyThread.Create;
  T.WaitForStart;
end;

 

Edited by Remy Lebeau
  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×