【问题标题】:Detect freed object called with TThread.ForceQueue() with delay检测用 TThread.ForceQueue() 延迟调用的释放对象
【发布时间】:2021-11-07 11:57:16
【问题描述】:

当您在TFrame 中并执行TThread.ForceQueue(nil, MyFrame.OneProc, 200) 时,如何在MyFrame.OneProc 过程中检查MyFrame 同时没有被破坏?

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

【问题讨论】:

    标签: delphi


    【解决方案1】:

    您可以使用 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,也有可能在排队过程运行之前释放帧。所以你需要保护你的框架也是这样的场景。

    【讨论】:

    • 感谢达莉亚!这个主意:) 我喜欢,接缝好!
    • 非常优雅的解决方案。
    【解决方案2】:

    您不能在已销毁的对象上调用方法。 首选的解决方案是,如果该方法尚未被调用,销毁对象之前简单地从队列中删除该方法。 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;
    

    【讨论】:

    • 这也是一个很好的答案,很遗憾我不能说 2 个答案被接受,因为 dalija 很好,你的解决方案也很好:(
    猜你喜欢
    • 2018-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多