pcplayer99 11 Posted November 12, 2019 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
David Heffernan 2345 Posted November 12, 2019 Why are you creating the thread suspended. What happens if you don't do that? Share this post Link to post
pcplayer99 11 Posted November 12, 2019 (edited) 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 November 12, 2019 by pcplayer99 Share this post Link to post
David Heffernan 2345 Posted November 12, 2019 Yes. What happens if you pass False and so don't create suspended? Share this post Link to post
aehimself 396 Posted November 12, 2019 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
Anders Melander 1784 Posted November 12, 2019 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
Dalija Prasnikar 1396 Posted November 12, 2019 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. 1 1 Share this post Link to post
David Heffernan 2345 Posted November 12, 2019 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. 1 Share this post Link to post
Anders Melander 1784 Posted November 12, 2019 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
David Heffernan 2345 Posted November 12, 2019 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
David Heffernan 2345 Posted November 12, 2019 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
Anders Melander 1784 Posted November 12, 2019 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
David Heffernan 2345 Posted November 12, 2019 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. 1 Share this post Link to post
Anders Melander 1784 Posted November 12, 2019 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
Remy Lebeau 1398 Posted November 12, 2019 (edited) 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 November 12, 2019 by Remy Lebeau 1 Share this post Link to post