延迟检测使用 TThread.ForceQueue() 调用的已释放对象

Detect freed object called with TThread.ForceQueue() with delay

当您在 TFrame 中并执行 TThread.ForceQueue(nil, MyFrame.OneProc, 200) 时,您如何在 MyFrame.OneProc 过程中检查 MyFrame 是否同时被销毁?

也就是说,这种常见的场景可以使用什么机制?

您可以使用 guardian 接口,该接口将是功能齐全的实例,您可以使用它来检查受保护对象是否同时被释放。

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;

然后你需要在你的框架中添加监护人字段

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;

但是你不能直接对框架的队列进行排队MyProc,你需要使用匿名方法并捕获那个守护变量,这样它的生命就会延长到框架的生命周期之外。

即使在 MyFrame 被释放后,引用计数也会使守护对象实例保持活动状态,并且其内存将被自动管理。

使用本地声明的Guardian接口变量并捕获该变量而不是直接捕获MyFrame.Guardian字段很重要,因为该字段地址在MyFrame被释放后将不再有效.

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

注意:即使您不加延迟地使用TThread.Queue,帧也有可能在排队过程运行之前被释放。所以在这种情况下你也需要保护好你的相框

您不能在已销毁的对象上调用方法。 首选 解决方案是简单地从队列中删除尚未调用的方法, 销毁对象之前。 TThread 有一个 RemoveQueuedEvents() 方法正是为了这个目的。

例如:

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

或者,改用框架的析构函数:

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

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