Delphi TThread под ARC (iOS) не выпускается
Каков правильный способ прекратить поток с помощью Delphi для iOS при управлении ARC?
Возьмем этот простой пример:
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;
Хорошо, нажатие Button1 вызовет поток. После запуска потока, если вы нажмете Button2, он отобразит значение RefCount 3! Ну, 1 является ссылкой на мою переменную FThread, и есть 2 дополнительные ссылки, которые TThread создает внутри... Я перекопал в исходный код и обнаружил, что RefCount здесь увеличен:
constructor TThread.Create(CreateSuspended: Boolean);
ErrCode := BeginThread(nil, @ThreadProc, Pointer(Self), FThreadID);
if ErrCode <> 0 then
raise EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(ErrCode)]);
{$ENDIF POSIX}
И здесь:
function ThreadProc(Thread: TThread): Integer;
var
FreeThread: Boolean;
begin
TThread.FCurrentThread := Thread;
Ну... После завершения потока (в моем случае через 5 секунд) RefCount уменьшится до 2 (потому что я установил FreeOnTerminate в TRUE, но если я не установил FreeOnTerminate в TRUE, RefCount будет все равно 3).
Увидеть проблему? Thread никогда не завершается, и деструктор никогда не вызывается, если я вызываю FThread := nil
, тогда RefCount должен уменьшаться от 2 до 1 (или от 3 до 2 в случае FreeOnTerminate = FALSE
), а поток никогда не будет выпущен под ARC...
Возможно, я что-то пропустил, потому что я работал с потоками без ARC... Итак, что мне здесь не хватает? Или есть ошибка в реализации TThread в ARC?
Возможно, это определение TThread
private class threadvar
FCurrentThread: TThread;
должно быть что-то вроде
private class threadvar
[Weak] FCurrentThread: TThread;
Ответы
Ответ 1
После некоторого копания в qc появляются следующие проблемы и обходные пути:
Параметры потока должны передаваться как const
function ThreadProc(Thread: TThread): Integer; <<-- pass_by_reference pushes
var up the ref_count.
FreeThread: Boolean;
begin
TThread.FCurrentThread := Thread;
Если бы вы передали его как const
, ref_count не увеличился бы до 3.
Обычно это не проблема, потому что ref_count уменьшается при выходе из функции, но здесь:
функция epilog никогда не вызывается, потому что pthread_exit() выпрыгивает из кода.
Это лишь часть решения, но нужно еще немного сделать...
Полное обходное решение от Dave Nottage
После долгих разворачиваний я придумал это возможное обходное решение:
Сделайте эти моды в подразделе Classes:
Изменение:
function ThreadProc(Thread: TThread): Integer;
в
function ThreadProc(const Thread: TThread): Integer;
и добавьте:
TThread.FCurrentThread := nil;
после этой строки:
if FreeThread then Thread.Free;
Переопределить DoTerminate
в потомке TThread, таким образом:
procedure TMyThread.DoTerminate;
begin
try
inherited;
finally
__ObjRelease;
end;
end;
Вызовите поток таким образом:
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;
Это (по крайней мере для меня, на устройстве) приводит к уничтожению потока при последующих вызовах.
ПРИМЕЧАНИЕ. В условиях ARC никогда не объявляйте ссылку на поток локально, потому что, когда она выходит из области видимости, поток уничтожается, и метод Execute никогда не вызывается, не говоря уже о других проблемах, которые он вызывает.