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
Senderfrom inside its own event. You don't know what else may still need to access theSenderafter the event handler exits.Case in point - if the
Senderin question is the object that owns theTIdHTTPServerwhich is triggering yourCallback()method, then you would be trying to free theSender, and thus free theTIdHTTPServer, whileTIdHTTPServeris still waiting for theCallback()to exit. SinceTIdHTTPServeris a multi-threaded component that fires itsOnCommand...events in worker threads, destroying theTIdHTTPServerin 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 theSender. For instance, by usingTThread.Queue(nil, Sender.Free);(just be aware thatTThread.Queue()runs its predicate in the main thread, so make sure yourSender's destruction is thread-safe in this example).