ARC(iOS)下的Delphi TThread未发布 [英] Delphi TThread under ARC (iOS) not being released

查看:285
本文介绍了ARC(iOS)下的Delphi TThread未发布的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



采取这个简单的例子:

  TMyThread = class(TThread)
protected
procedure Execute;覆盖
public
析构函数Destroy;覆盖
结束

TForm2 = class(TForm)
Button1:TButton;
Button2:TButton;
Button3:TButton;
procedure Button1Click(Sender:TObject);
程序Button2Click(发件人:TObject);
程序Button3Click(发件人:TObject);
private
FThread:TMyThread;
public
end;

{TMyThread}
析构函数TMyThread.Destroy;
begin

ShowMessage('Destroy');

继承了Destroy;

end;

程序TMyThread.Execute;
开始

睡眠(5000);

end;

{TForm2}
程序TForm2.Button1Click(Sender:TObject);
begin
FThread:= TMyThread.Create(TRUE);
FThread.FreeOnTerminate:= TRUE;
FThread.Start;
结束

程序TForm2.Button2Click(Sender:TObject);
begin
ShowMessage(FThread.RefCount.ToString);
结束

程序TForm2.Button3Click(发件人:TObject);
begin
FThread:= nil;
结束

好的,按下Button1会产生一个线程。线程启动后,如果您单击Button2,它将显示一个RefCount值3!那么,1是对我的FThread变量的引用,还有另外两个TThread在内部创建的引用...我已经挖掘了源代码,发现RefCount在这里增加了:

 构造函数TThread.Create(CreateSuspended:Boolean); 

ErrCode:= BeginThread(nil,@ThreadProc,指针(自身),FThreadID);
如果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)。



看到问题?线程从未完成,析构函数从未被调用,如果我调用 FThread:= nil ,那么RefCount应该从2减为1(或者从3到2, code> FreeOnTerminate = FALSE ),线程永远不会在ARC下发布...



也许我没有任何东西,因为我习惯于使用没有ARC的线程...所以,我在这里缺少什么?还是在ARC下的TThread实现中有错误?



也许这个TThread的定义

 code> private class threadvar 
FCurrentThread:TThread;

应该像

  private class threadvar 
[Weak] FCurrentThread:TThread;


解决方案

在qc中挖掘以下问题和解决方法



线程参数应以 const

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

如果您将其作为 const 将不会升到3.
通常这不是问题,因为ref_count在函数退出时减少,但这里:


功能epilog从来没有发生过,因为pthread_exit()跳出了代码


这只是解决方案的一部分,但还需要做更多的工作...



Dave Nottage的全面解决方法


我已经想到了这个潜在的解决方法:


将这些MOD设置为Classes单位:
更改:

  function ThreadProc(Thread:TThread):Integer; 

to:

 code> function ThreadProc(const Thread:TThread):Integer; 

并添加:

  TThread.FCurrentThread:= nil; 

此行后:

 如果FreeThread然后Thread.Free; $ t $ b  

在TThread后代覆盖 DoTerminate

 程序TMyThread.DoTerminate; 
begin
try
inherited;
finally
__ObjRelease;
结束
结束

因此调用线程:

  FMyThread.Free; //这首先不会做任何事情,因为引用将为nil 
FMyThread:= TMyThread.Create(True);
//不要设置FreeOnTerminate
FMyThread.OnTerminate:= ThreadTerminate;
FMyThread.Resume;




这(至少对我来说,在设备上)导致线程在后续调用中被销毁。



注意:在ARC条件下,永远不会在本地声明对线程的引用,因为当它脱离范围,线程被破坏,并且Execute方法从不被调用,更不用说其导致的其他问题。



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.

这篇关于ARC(iOS)下的Delphi TThread未发布的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆