So, say we have a class like this:
TFieldsReadyEvent = procedure(Sender: TObject; Code, AuthorizatedID: string) of object;
TCatcherServer = class(TServer)
private
FOnFieldsReady: TFieldsReadyEvent;
FCode: string;
FAuthoID: string;
FAuthorizationType: TAuthorizationType;
function GetAuthorizationType: string;
function GetCode: string;
function GetEntityID: string;
public
property OnFieldsReady: TFieldsReadyEvent read FOnFieldsReady write FOnFieldsReady;
property Code: string read GetCode;
property EntityID: string read GetEntityID;
property AuthorizationType: string read GetAuthorizationType;
procedure Callback(Session: TIdContext; Req: TIdHTTPRequestInfo; Res: TIdHTTPResponseInfo); override;
end;
Where the Callback
procedure looks like this:
procedure TCatcherServer.Callback(Session: TIdContext;
Req: TIdHTTPRequestInfo; Res: TIdHTTPResponseInfo);
begin
inherited Callback(Session, Req, Res);
if (Req.Params.Values['code'] <> '') and ((Req.Params.Values['shop_id'] <> '') or (Req.Params.Values['main_account_id'] <> '')) then
begin
Res.ResponseNo := 200;
Res.ResponseText := '{"message":"continue on the application"}';
FCode := Req.Params.Values['code'];
if (Req.Params.Values['shop_id'] <> '') then
begin
FAuthorizationType := atShopAuthorization;
FAuthoID := Req.Params.Values['shop_id'];
end else
begin
FAuthorizationType := atMainAccountAuthorization;
FAuthoID := Req.Params.Values['main_account_id'];
end;
FOnFieldsReady(Self, FCode, FAuthoID);
end else
begin
Res.ResponseNo := 400;
Res.ResponseText := '{"message":"Something went wrong."}';
end;
end;
Is it a correct approach to have in my form an event handler that is able to Free the TCatcherServer instance? Example as follows:
{Form1 public declarations}
procedure FieldsReadyHandler(Sender: TObject; Code, AuthorizatedID: string);
procedure TForm1.Button1Click(Sender: TObject);
begin
VarServer := TCatcherServer.Create;
VarServer.Listen(7070);
VarServer.OnFieldsReady := FieldsReadyHandler;
end;
procedure TForm1.FieldsReadyHandler(Sender: TObject; Code,
AuthorizatedID: string);
begin
ServerLog.Lines.Add(Code);
ServerLog.Lines.Add(AuthorizatedID);
ServerLog.Lines.Add((Sender as TCatcherServer).AuthorizationType);
Sender.Free;
end;
TL;DR Is it the correct approach to use events to free the sender in cases like this (where after the event is triggered I got the data I needed and don't need the Sender anymore). If not, what's the correct way of doing so?
It is usually NOT safe to free the Sender
from inside its own event. You don't know what else may still need to access the Sender
after the event handler exits.
Case in point - if the Sender
in question is the object that owns the TIdHTTPServer
which is triggering your Callback()
method, then you would be trying to free the Sender
, and thus free the TIdHTTPServer
, while TIdHTTPServer
is still waiting for the Callback()
to exit. Since TIdHTTPServer
is a multi-threaded component that fires its OnCommand...
events in worker threads, destroying the TIdHTTPServer
in its own event would cause a deadlock.
If you must free the Sender
, you should do so asynchronously so you give the caller/RTL extra time to finish using the Sender
. For instance, by using TThread.Queue(nil, Sender.Free);
(just be aware that TThread.Queue()
runs its predicate in the main thread, so make sure your Sender
's destruction is thread-safe in this example).