What is a proper way to terminate a thread using Delphi for iOS under ARC management?
Take this simple example:
TMyThread = class(TThread)
protected
procedure Execute; override;
public
destructor Destroy; override;
end;
TForm2 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
FThread: TMyThread;
public
end;
{ TMyThread }
destructor TMyThread.Destroy;
begin
ShowMessage('Destroy');
inherited Destroy;
end;
procedure TMyThread.Execute;
begin
Sleep(5000);
end;
{ TForm2 }
procedure TForm2.Button1Click(Sender: TObject);
begin
FThread := TMyThread.Create(TRUE);
FThread.FreeOnTerminate := TRUE;
FThread.Start;
end;
procedure TForm2.Button2Click(Sender: TObject);
begin
ShowMessage(FThread.RefCount.ToString);
end;
procedure TForm2.Button3Click(Sender: TObject);
begin
FThread := nil;
end;
Ok, pressing Button1 will spawn a thread. After thread is started, if you click Button2 it will display a RefCount value of 3!! Well, 1 is the reference to my FThread variable, and there are 2 additional references that TThread creates internally... I have digged into source code and found that RefCount is increased here:
constructor TThread.Create(CreateSuspended: Boolean);
ErrCode := BeginThread(nil, @ThreadProc, Pointer(Self), FThreadID);
if ErrCode <> 0 then
raise EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(ErrCode)]);
{$ENDIF POSIX}
And here:
function ThreadProc(Thread: TThread): Integer;
var
FreeThread: Boolean;
begin
TThread.FCurrentThread := Thread;
Well... After thread is finished (In my case, after 5 seconds), the RefCount will decrease to 2 (Because I have set FreeOnTerminate to TRUE, but if I don´t set FreeOnTerminate to TRUE, the RefCount will still be 3).
See the problem? Thread is never finished and destructor is never called, if I call FThread := nil
, then, the RefCount should decrease from 2 to 1 (Or from 3 to 2 in case FreeOnTerminate = FALSE
), and thread is never released under ARC...
Maybe I´m missing something because I´m used to working with threads with no ARC... So, what am I missing here? Or is there a bug in TThread implementation under ARC?
Maybe this definition of TThread
private class threadvar
FCurrentThread: TThread;
should be something like
private class threadvar
[Weak] FCurrentThread: TThread;
After some digging in qc the following issues and workaround show up:
Thread parameters should be passed as const
function ThreadProc(Thread: TThread): Integer; <<-- pass_by_reference pushes
var up the ref_count.
FreeThread: Boolean;
begin
TThread.FCurrentThread := Thread;
Had you passed it as const
the ref_count would have not gone up to 3.
Normally this is not a problem, because the ref_count gets decreased on exit of the function, but here:
the function epilog is never exectued because pthread_exit() jumps out of the code.
This is only part of the solution though, quite a bit more needs to be done...
Full workaround by Dave Nottage
After much fiddling around, I've come up with this potential workaround:
Make these mods to the Classes unit: Change:
function ThreadProc(Thread: TThread): Integer;
to:
function ThreadProc(const Thread: TThread): Integer;
and add:
TThread.FCurrentThread := nil;
after this line:
if FreeThread then Thread.Free;
Override DoTerminate
in the TThread descendant, thus:
procedure TMyThread.DoTerminate;
begin
try
inherited;
finally
__ObjRelease;
end;
end;
Call the thread thus:
FMyThread.Free; // This will do nothing the first time around, since the reference will be nil
FMyThread := TMyThread.Create(True);
// DO NOT SET FreeOnTerminate
FMyThread.OnTerminate := ThreadTerminate;
FMyThread.Resume;
This (at least for me, on the device) results in the thread being destroyed on subsequent calls.
NOTE: Under ARC conditions, never declare a reference to a thread locally, because when it falls out of scope, the thread is destroyed and the Execute method is never called, not to mention other problems it causes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With