【问题标题】:How to destroy/free an anonymousThread如何销毁/释放和匿名线程
【发布时间】:2016-10-08 11:26:52
【问题描述】:

在 Windows 上,要停止和销毁匿名线程,我只需执行 FmyTask.free => 这将调用 destroy => 并且在destroy 内部将设置 terminate = true 并调用 waitfor 等待任务完成 => 最后清理使用的内存

但在 ARC 上,一切都不一样了 :( 我用这个代码:

TMyObject
private
  FMyTask: TThread;
public
  destructor Destroy; override;
  procedure DoSomething; 
end;

destructor TMyObject.Destroy;
begin
  FMyTask.free;  // << will do nothing because FMyTask.refcount = 2 !! how it's possible ?
  FMyTask:= nil;
  inherited;
end;

procedure TMyObject.DoSomething;
begin

  FMyTask:= Thread.CreateAnonymousThread(
    procedure
    begin
      sleep(10000000);
    end);
  FMyTask.FreeOnTerminate := False;
  FMyTask.Start;

end;

我什么都不做

MyObject := TmyObject.create;
MyObject.DoSomething;
MyObject.free;
MyObject := nil;

但是如你所见,在 TmyObject 的 onDestroy 中,FMyTask 的引用计数为 2 !!所以这意味着 FMyTask 不会被 TmyObject 破坏(引用计数将减少到 1)(但稍后),你想象所有可能导致的错误:(

很明显,对 fMyTask 的引用保存在这个函数中:

function ThreadProc(const Thread: TThread): Integer;
var
  FreeThread: Boolean;
{$IFDEF MACOS}
  pool: Pointer;
{$ENDIF MACOS}
begin
{$IFDEF AUTOREFCOUNT}
  Thread.__ObjAddRef; // this ensures the instance remains for as long as the thread is running
{$ENDIF}
  TThread.FCurrentThread := Thread;
{$IF Defined(POSIX)}
  if Thread.FSuspended then
    pthread_mutex_lock(Thread.FCreateSuspendedMutex);
{$ENDIF POSIX}
{$IFDEF MACOS}
  // Register the auto release pool
  pool := objc_msgSend(objc_msgSend(objc_getClass('NSAutoreleasePool'),
                                    sel_getUid('alloc')), sel_getUid('init'));
{$ENDIF MACOS}
  try
    Thread.FStarted := True;
    if not Thread.Terminated then
    try
      Thread.Execute;
    except
      Thread.FFatalException := AcquireExceptionObject;
    end;
  finally
    Result := Thread.FReturnValue;
    FreeThread := Thread.FFreeOnTerminate;
    Thread.DoTerminate;
    Thread.FFinished := True;
    SignalSyncEvent;
    if FreeThread then
    begin
      Thread.DisposeOf;
{$IFDEF AUTOREFCOUNT}
      Thread.__ObjRelease; // This will clear the thread reference that was added by setting FreeOnTerminate.
{$ENDIF}
    end;
{$IFDEF AUTOREFCOUNT}
    Thread.__ObjRelease; // This will clear the thread reference we added above. This may initiate disposal.
{$ENDIF}
{$IFDEF USE_LIBICU}
    // Destroy Collator Cache
    ClearCollatorCache;
{$ENDIF}
{$IF Defined(MSWINDOWS)}
    EndThread(Result);
{$ELSEIF Defined(POSIX)}
{$IFDEF MACOS}
    // Last thing to do in thread is to drain the pool
    objc_msgSend(pool, sel_getUid('drain'));
{$ENDIF MACOS}
{$IFDEF ANDROID}
    // Detach the NativeActivity virtual machine to ensure the proper relase of JNI context attached to the current thread
    PJavaVM(System.JavaMachine)^.DetachCurrentThread(PJavaVM(System.JavaMachine));
{$ENDIF ANDROID}
    // Directly call pthread_exit since EndThread will detach the thread causing
    // the pthread_join in TThread.WaitFor to fail.  Also, make sure the EndThreadProc
    // is called just like EndThread would do. EndThreadProc should not return
    // and call pthread_exit itself.
    if Assigned(EndThreadProc) then
      EndThreadProc(Result);
    pthread_exit(Result);
{$ENDIF POSIX}
  end;
end;

这是正常行为吗?如果是的话,我是唯一一个认为 arc 比 delphi 更糟糕的人?

【问题讨论】:

  • 匿名线程应该定期检查Terminated。并且在销毁对象的时候,调用FMyTask.Terminate; FMyTask.WaitFor; FMyTask.Free;,应该保证线程已经停止执行,当arc觉得合适的时候就会被释放。
  • 谢谢 LU,但你不明白我的问题,我不关心它在 onexecute 中做了什么,我关心当我执行 FmyTask.free 和 fMyTask := nil 然后 FmyTask实际上不会被破坏,这是与 windows/non arc 完全不同的行为
  • 我想我明白了。但 ARC 将负责释放匿名线程。你做过泄漏测试吗?
  • @LURD 我曾经精确地连续调用这 3 行,直到这里有人坚持不使用 WaitFor 因为TThread.Destroy 已经调用它。
  • 是的,肯定 arc 会负责释放匿名线程,但这就是问题所在,因为匿名线程使用 TmyObject 的成员/字段(不是在我的简单示例中,而是在现实生活中) ,如果 tMyObject 已经免费,那么问题... :( 当然,他们有很多方法可以纠正这个问题,但我只想指出在 Windows(非弧)上完美运行的代码在 ARC 上根本不起作用 :(

标签: delphi automatic-ref-counting firemonkey


【解决方案1】:

ARC 必须保留对线程的额外引用,以免在创建者超出范围时过早释放它。

在非 ARC 下,当您调用 FMyThread.Free 时,将调用 Destroy 析构函数,随后调用 Terminate; 和 WaitFor;

使用 ARC,调用 Free 会减少引用计数并检查是否为零。由于还剩下一个引用,所以不调用析构函数。

这意味着释放线程(FreeOnTerminate=false)的模式应该是:

fMyThread.Terminate;
fMyThread.WaitFor;  // Wait for the thread to finish
fMyThread.Free;

这意味着 ARC 获取的额外引用被删除,并且线程对象将在创建者超出范围后随时释放。


请注意,这种释放线程的模式(使用FreeOnTerminate=false)适用于所有编译器,并且在终止非 ARC 区域中的线程时还可以避免罕见的竞争条件。


所有设置为FreeOnTerminate = true 的线程的一般规则是它们不应通过引用从外部访问。

【讨论】:

  • 除了CreateAnonymousThread 使用FreeOnTerminate=True 不安全与WaitFor 或Free 一起使用。
  • @RemyLebeau,是的 FreeOnTerminate 对于匿名线程默认为 true,但在这种情况下,它在执行前设置为 false。
猜你喜欢
  • 2010-10-29
  • 2011-07-19
  • 2019-10-01
  • 2019-05-02
  • 2014-05-29
  • 1970-01-01
  • 2018-02-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多