Delphi TThread bajo ARC (iOS) no se lanzó

¿Cuál es la forma correcta de terminar un hilo usando Delphi para iOS bajo la administración de ARC?

Tome este sencillo ejemplo:

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, presionar Button1 generará un hilo. Una vez iniciado el hilo, si hace clic en Button2, se mostrará un valor RefCount de 3. Bueno, 1 es la reference a mi variable FThread, y hay 2 references adicionales que TThread crea internamente … He cavado en el código fuente y he encontrado que RefCount aumenta aquí:

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

Y aquí:

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

Bueno … Después de terminar el hilo (en mi caso, después de 5 segundos), el RefCount disminuirá a 2 (porque he establecido FreeOnTerminate en TRUE, pero si no establezco FreeOnTerminate en TRUE, el RefCount seguirá siendo 3 )

¿Ve el problema? El hilo nunca termina y el destructor nunca se llama, si llamo FThread := nil , entonces, el RefCount debería disminuir de 2 a 1 (O de 3 a 2 en caso de FreeOnTerminate = FALSE ), y el hilo nunca se libera debajo de ARC .. .

Tal vez me falta algo porque estoy acostumbrado a trabajar con subprocesss sin ARC … Entonces, ¿qué me falta aquí? ¿O hay un error en la implementación de TThread en ARC?

Tal vez esta definición de TThread

 private class threadvar FCurrentThread: TThread; 

debe ser algo así

 private class threadvar [Weak] FCurrentThread: TThread; 

Después de algunas excavaciones, aparecen los siguientes problemas y la solución:

Los parameters de subprocess deben pasarse como const

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

Si lo hubieras pasado como const el ref_count no habría subido a 3. Normalmente, esto no es un problema, porque el ref_count disminuye al salir de la function, pero aquí:

la function epilog nunca se evalúa porque pthread_exit () salta del código .

Esto es solo una parte de la solución, hay que hacer un poco más …

Solución completa de Dave Nottage

Después de juguetear mucho, he encontrado esta solución potencial:

Realice estos cambios a la unidad Clases: Cambiar:

 function ThreadProc(Thread: TThread): Integer; 

a:

 function ThreadProc(const Thread: TThread): Integer; 

y añada:

 TThread.FCurrentThread := nil; 

después de esta línea:

 if FreeThread then Thread.Free; 

DoTerminate en el descendiente TThread, así:

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

Llame al hilo así:

 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; 

Esto (al less para mí, en el dispositivo) hace que el hilo se destruya en llamadas posteriores.

NOTA: En las condiciones ARC, nunca declare una reference a un subprocess localmente, ya que cuando cae fuera del scope, se destruye el subprocess y nunca se llama al método Execute, sin mencionar otros problemas que provoca.