Подтвердить что ты не робот

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;
4b9b3361

Ответ 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 никогда не вызывается, не говоря уже о других проблемах, которые он вызывает.