Link to home
Start Free TrialLog in
Avatar of Marius0188
Marius0188

asked on

Delphi 7 & Indy 10 OnCommand Event and VCL Components Thread Safe?

Dear Experts,

I am using Delphi 7 and Indy 10 components.
In the TidCommandHandler component I have created several commands and in their OnCommand events I have code that updates VCL components.

If I am not mistaken the events of the TidCommandHandler's commands occurs in a Threads handled by Indy itself. Do I need to take any special caution when updating VCL components in these events? I experience some problem errors, "EOSERROR with Code 1400, invalid window handle" and I am sure it is because I am not updating the VCL in a thread safe manner.

Can you please help me and show me how should I update VCL components in a safe manner in these events?

Thanks in advance!
ASKER CERTIFIED SOLUTION
Avatar of TheRealLoki
TheRealLoki
Flag of New Zealand image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of rafaelrgl
rafaelrgl

I have same problem experts, please help us. I will open other question to give you guys more 500 points. Please I'm waiting.
Tell me what vcl components you are updating, and what information you are passing to them, and i will show you how to do it in a thread safe manner.
Would also be handy if you could paste your main indy command method here.
Is that Safe???????

/////////////////////////////////////////////////////////////////////////////////////////////

procedure TFPrinc.IdCmdTCPServer1CommandHandlers5Command(
  ASender: TIdCommand);
var
  datetime, msg : string;
begin
    datetime := Asender.Context.Connection.IOHandler.ReadLn;
    msg := FindComp(ASender.Context.Connection.Socket.Binding.PeerIP) + ' - Nucleo Client ' +  datetime;
    HistoryAdd(msg,1);
    BD.SPSecurityAdd.ParamByName('@idfunc').AsString := FuncionarioID;
    BD.SPSecurityAdd.ParamByName('@idcaixa').AsString := StatusBar1.Panels[2].Text;
    BD.SPSecurityAdd.ParamByName('@data').AsString := FormatDateTime('MM/dd/yyyy hh:nn:ss AM/PM',now());
    BD.SPSecurityAdd.ParamByName('@mensagem').AsString := msg;
    Bd.SPSecurityAdd.ExecProc;

    FEMail_Send.Mensagem.Clear;
    FEMail_Send.Mensagem.Lines.Add('Security Email');
    FEMail_Send.Mensagem.Lines.Add('   ');
    FEMail_Send.Mensagem.Lines.Add(Msg);
    FEMail_Send.Mensagem.Lines.Add('   ');
    FEMail_Send.Mensagem.Lines.Add('   ');
    FEMail_Send.Mensagem.Lines.Add('Funcionario..........: ' + Funcionario);
    FEMail_Send.Mensagem.Lines.Add('Caixa................: ' + StatusBar1.Panels[2].Text);
    FEMail_Send.Send;

/////////////////////////////////////////////////////////////////////////////////////////////
well it's safe, but looking at your code, probably not the best solution for you.
I thought you meant things like a log (TMemo's etc).
If you are using SQL queries, etc. you should be defining them inside thread instead, as long as they do not update a component in the main vcl.

here's some of my code for doing this. It's from a IdTCPServer, but it should be the same for the CMD one

type
  TSpecificClientConnection = class(TServerClientWinSocket)
  public
      ID:          string; // just the unique id we create for each connection
      StatusBarText: string; // you could fill this when the client connects...
      ClientTimer: TTimer; // just an example of a component
      Constructor CreateWithSettings(ID_: string; statusbartext_: string);
      Destructor Destroy; override;
  end;

{ TClientSpecificData }

constructor TClientSpecificData.CreateWithSettings(ID_, statusbartext_: string);
    begin
        Inherited Create;
        ID := ID_;
        statusbartext := statusbartext_;
        ClientTimer := TTimer.create(nil); // just an example...
    end;

destructor TClientSpecificData.Destroy;
    begin
        ClientTimer.Free;
        inherited;
    end;


procedure TfServerMain.IdTCPServer1Connect(AThread: TIdPeerThread);
var
    ID_: string;
begin
    ID_ := AThread.Connection.Socket.Binding.PeerIP + ':' + IntToStr(AThread.Connection.Socket.Binding.PeerPort);
    AThread.Data := TClientSpecificData.CreateWithSettings(ID_);
    AThread.Connection.Tag := integer(AThread.Data); //can also store our data object here as long as we type cast it back
...
end;

procedure TfServerMain.IdTCPServer1Disconnect(AThread: TIdPeerThread);
begin
    if assigned(AThread.Data) then
    begin
        (AThread.Data as TClientSpecificData).Free;
        AThread.Data := nil;
    end;
end;

you can then access it in various places by typecasting it back
eg. (AThread.Data as TClientSpecificData).ClientTimer (or StatusBartext)


To access the StatusBar like you are doing is a bit different. This is one of the things you should not be doing in threads really.
However, you can get around it by using a TCriticalSection, or windows messages, or indys syncing methods (a little trickier actually)
for a simple, TCriticalSection, in a shared unit, declare the following
mycrit: TCriticalSection
crittext: string;

put the following in your form's create (or some other more relevent place)
mycrit := TCriticalSection.Create;
and in the form's destroy (or some other more relevent place)
mycrit.free;

then, EVERY TIME you want to change or read the value of "crittext" you must do it like this
mycrit.enter;
try
  s := crittext;
finally
  mycrit.Leave;
end;

note: you must do this in teh main vcl as well as in the threads.

hth, Loki
Forced accept.

Computer101
EE Admin