Home > Blockchain >  Detect freed object called with TThread.ForceQueue() with delay
Detect freed object called with TThread.ForceQueue() with delay

Time:11-08

When you are in a TFrame and you do TThread.ForceQueue(nil, MyFrame.OneProc, 200) how can you check in the MyFrame.OneProc procedure that MyFrame was not destroyed in the mean time?

In other words, what mechanism can be used in such common scenario?

CodePudding user response:

You can use guardian interface that will be fully functioning instance you can use to check whether guarded object is released in the meantime.

type
  IGuardian = interface
    function GetIsDismantled: Boolean;
    procedure Dismantle;
    property IsDismantled: Boolean read GetIsDismantled;
  end;

  TGuardian = class(TInterfacedObject, IGuardian)
  private
    FIsDismantled: Boolean;
    function GetIsDismantled: Boolean;
  public
    procedure Dismantle;
    property IsDismantled: Boolean read GetIsDismantled;
  end;


procedure TGuardian.Dismantle;
begin
  FIsDismantled := True;
end;

function TGuardian.GetIsDismantled: Boolean;
begin
  Result := FIsDismantled;
end;

And then you need to add guardian field in your frame

type
  TMyFrame = class(TFrame)
  private
    FGuardian: IGuardian;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property Guardian: IGuardian read FGuardian;
  end;

constructor TMyFrame.Create(AOwner: TComponent);
begin
  inherited;
  FGuardian := TGuardian.Create;
end;

destructor TMyFrame.Destroy;
begin
  // prevent AV when destroying partially
  // constructed instance
  if Assigned(FGuardian) then
    FGuardian.Dismantle;
  inherited;
end;

But you cannot directly queue frame's MyProc, you need to use anonymous methods and capture that guardian variable so its life will be extended beyond the lifetime of the frame.

Reference counting will keep the guardian object instance alive even after MyFrame is released and its memory will be automatically managed.

It is important to use locally declared Guardian interface variable and capture that variable instead of directly capturing MyFrame.Guardian field because that field address will no longer be valid after MyFrame is released.

procedure CallMyProc;
var
  Guardian: IGuardian;
begin
  Guardian := MyFrame.Guardian;
  TThread.ForceQueue(nil, 
    procedure
    begin
      if Guardian.IsDismantled then
        Exit;
      MyFrame.OneProc;
    end, 200);
end; 

Note: Even if you use TThread.Queue without a delay, it is possible that frame will be released before queued procedure runs. So you need to protect your frame is such scenarios, too.

CodePudding user response:

You can't call a method on an object that has been destroyed. The preferred solution is to simply remove the method from the queue if it hasn't been called yet, before destroying the object. TThread has a RemoveQueuedEvents() method for exactly that purpose.

For example:

TThread.ForceQueue(nil, MyFrame.OneProc, 200);
...
TThread.RemoveQueuedEvents(MyFrame.OneProc);
MyFrame.Free;

Alternatively, use the frame's destructor instead:

TThread.ForceQueue(nil, MyFrame.OneProc, 200);
...

destructor TMyFrame.Destroy;
begin
  TThread.RemoveQueuedEvents(OneProc);
  inherited;
end;
  • Related