Frage Delphi TThread unter ARC (iOS) wird nicht veröffentlicht


Was ist ein geeigneter Weg, um einen Thread mit Delphi für iOS unter ARC-Management zu beenden?

Nehmen Sie dieses einfache Beispiel:

  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, durch Drücken von Button1 erscheint ein Thread. Nachdem der Thread gestartet wurde, wird beim Klicken auf Button2 ein RefCount-Wert von 3 angezeigt. Nun, 1 ist der Verweis auf meine FThread-Variable, und es gibt 2 zusätzliche Referenzen, die TThread intern erstellt ... Ich habe im Quellcode gegraben und festgestellt, dass RefCount hier erhöht ist:

constructor TThread.Create(CreateSuspended: Boolean);

  ErrCode := BeginThread(nil, @ThreadProc, Pointer(Self), FThreadID);
  if ErrCode <> 0 then
    raise EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(ErrCode)]);
  {$ENDIF POSIX}

Und hier:

function ThreadProc(Thread: TThread): Integer;
var
  FreeThread: Boolean;
begin
  TThread.FCurrentThread := Thread;

Nun ... Nachdem der Thread beendet ist (in meinem Fall nach 5 Sekunden), wird der RefCount auf 2 reduziert (Weil ich FreeOnTerminate auf TRUE gesetzt habe, aber wenn ich FreeOnTerminate nicht auf TRUE setze, bleibt RefCount immer 3 ).

Siehst du das Problem? Thread ist nie fertig und Destruktor wird nie aufgerufen, wenn ich anrufe FThread := nilDann sollte der RefCount von 2 auf 1 sinken (oder von 3 auf 2) FreeOnTerminate = FALSE), und Thread wird nie unter ARC freigegeben ...

Vielleicht verpasse ich etwas, weil ich es gewohnt bin, mit Threads ohne ARC zu arbeiten ... Also, was fehlt mir hier? Oder gibt es einen Fehler in der TThread-Implementierung unter ARC?

Vielleicht diese Definition von TThread

private class threadvar
  FCurrentThread: TThread;

sollte etwas wie sein

private class threadvar
  [Weak] FCurrentThread: TThread;

13
2018-06-10 19:35


Ursprung


Antworten:


Nach einigem Graben in qc tauchen folgende Probleme und Abhilfe auf:

Thread-Parameter sollten als übergeben werden const 

function ThreadProc(Thread: TThread): Integer;  <<-- pass_by_reference pushes
var                                                  up the ref_count.
  FreeThread: Boolean;
begin
  TThread.FCurrentThread := Thread;

Hatten Sie es als bestanden const Der ref_count wäre nicht auf 3 gestiegen. Normalerweise ist das kein Problem, weil ref_count beim Beenden der Funktion verringert wird, aber hier:

der Funktionsepilog wird niemals ausgeführt, da pthread_exit () aus dem Code springt.

Dies ist nur ein Teil der Lösung, aber es muss noch einiges getan werden ...

Vollständige Abhilfe von Dave Nottage 

Nachdem ich viel herumgespielt habe, habe ich mir diese mögliche Problemumgehung ausgedacht:

Mache diese Mods zur Einheit der Klassen: Veränderung:

function ThreadProc(Thread: TThread): Integer;

zu:

function ThreadProc(const Thread: TThread): Integer;

und füge hinzu:

TThread.FCurrentThread := nil;

nach dieser Zeile:

if FreeThread then Thread.Free;

Überschreiben DoTerminate im TThread-Nachkomme also:

procedure TMyThread.DoTerminate;
begin
  try
    inherited;
  finally
    __ObjRelease;
  end;
end;

Nenne den Thread so:

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;

Dies führt (zumindest für mich, auf dem Gerät) dazu, dass der Thread bei nachfolgenden Aufrufen zerstört wird.

HINWEIS: Unter ARC Bedingungen, noch nie einen Verweis auf einen Thread lokal deklarieren, da der Thread zerstört wird und die Execute-Methode nie aufgerufen wird, ganz zu schweigen von anderen Problemen, die er verursacht.


3