延迟检测使用 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;
当您在 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;