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;