Search code examples
delphiautomatic-ref-countingfiremonkey

How to destroy/free an anonymousThread


On windows, to stop and destroy an anonymous thread, i simply do FmyTask.free => that will call destroy => and that inside the destroy will set terminate = true and call waitfor to wait the task is finished => and finally clean the memory used

but on ARC, everything is different :( i use this code :

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;

and i do nothing else than just

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

but as you see, in the onDestroy of TmyObject, FMyTask have a refcount of 2 !! so it's mean that the FMyTask will be not destroyed (refcount will be decreased to 1) with TmyObject (but later) and you imagine all the bug that can results :(

it's sean that a reference to fMyTask is keep inside this function :

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;

is it a normal behavior ? if yes, i m the only one to think that arc is the worse think of delphi ?


Solution

  • ARC must keep an extra reference to the thread in order not free it too soon if the creator should go out of scope.

    Under non-ARC, when you call FMyThread.Free, the Destroy destructor is called and subsequently Terminate; and WaitFor;

    With ARC, calling Free decrements ref count and checks for zero. Since there is still one reference left, the destructor is not called.

    This means that the pattern to free a thread (with FreeOnTerminate=false) should be:

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

    This means that the extra reference taken by ARC is removed and the thread object will be released any time after the creator goes out of scope.


    Note that this pattern for freeing a thread (with FreeOnTerminate=false) works in all compilers, and also avoids rare race conditions when terminating threads in non-ARC land.


    General rule for all threads that are set to FreeOnTerminate = true is they should not be accessed from outside by reference.