Reputation: 1109
I have a server that sends it's status every 0.1 seconds. I use this code to access UI and show the result on the client program.
procedure TModules.TCPServerExecute(AContext: TIdContext);
begin
Buffer:= AContext.Connection.IOHandler.ReadLn();
TIdNotify.NotifyMethod( ServerExecute );
end;
But some packet is not received by ServerExecute
. I change the code and used TIdSync.SynchronizeMethod
. The problem solved:
procedure TModules.TCPServerExecute(AContext: TIdContext);
begin
Buffer:= AContext.Connection.IOHandler.ReadLn();
TIdSync.SynchronizeMethod( ServerExecute );
end;
I have read on this site that TIdSync.SynchronizeMethod may produce deadlock.
So I want to know what is the problem with TIdNotify.NotifyMethod
. Also I see an answer that suggest using a Timer
to synchronize UI and don't using Notify() nor Synchronize() method.
Also I must mention that the program does rest of the work inside ServerExecute
.
As you see I'm narrowing down the code to the following. I must mention that I executed the program with exact the following code and it already has the problem and doesn't get desired results:
procedure TModules.ServerExecute;
var
I: Word;
tmp, Packet, Cmd:string;
CheckSum: Boolean;
begin
//try
frmDiag.DataLog.Lines.Add(Buffer);
{ExplodeStr(Buffer, Cmd, Packet);
if Cmd='status' then
begin
//ExplodeStr(Packet, Cmd, Packet);
if trim(Packet)='' then
raise Exception.Create('Empty packet received.');
//Prepare for log
{tmp:='';
for I := 1 to Length(Packet) do
begin
if (Ord(Packet[I])>=48) and (Ord(Packet[I])<=122) then
tmp:=tmp+Packet[I]+''
else
tmp:=tmp+IntToStr(Ord(Packet[I]))+'';
end;
//frmITCAMain.Memo1.Lines.Add(Packet);
CheckSum:=ParsePackets(Packet);
IntersectionsList.Int.CallNotifier; //Call the notifier to execute assigned proc
if frmLoader.Visible=true then
with frmLoader do
begin
if
//(Packet[1]='X') or //Server responce
(Packet[1]<>'l') and (Packet[1]<>'s') and (Packet[1]<>'e')and //ignore general packet
(
(Req[1]<>'f') //Only receive ACK
or
((Req[1]='f')and( (Req[2]='m')or(Req[2]='a')or(Req[2]='b') ))
or
( //Receive Data+ACK
(Req[1]='f')and( (Req[2]='g')or(Req[2]='h')or(Req[2]='p')or(Req[2]='j') )
and
(Packet[1]<>'k') //Ignore ACK
)
)
then
begin
if CheckSum then
begin
Res:= Packet;
Confirm;
end
else
begin
if Packet='n' then //Checksum failure
Fail
else
SendPacket; //Resend.
//lblError.Visible:=true;
end;
end;
end;
if (Packet[1]='g') or (Packet[1]='h') or (Packet[1]='p') or
(Packet[1]='j') or (Packet[1]='k') then
frmIntDetails.memReceived.Lines.Text:=tmp;
end
else if Cmd='server' then
begin
with frmLoader do
begin
if Visible=false then exit;
Res:= Packet;
if Copy(Res, 1, 2)='ok' then
Confirm
else
Cancel;
end;
end
else
ClientLog.Add(Buffer);
except on E: Exception do
begin
if E.Message='Connection Closed Gracefully.' then
ClientLog.Add('X:Connection closed(Gracefully)')
else
ClientLog.Add(E.Message+' Buffer="'+Buffer+'"');
end;
end;
//Memo2.Lines.Add( FloatToStr(TimeStampToMSecs(DateTimeToTimeStamp(Now))));
}
end;
frmDiag.DataLog
is a TMemo
component.
Inside frmDiag.DataLog
for example the following list is the result that i expected (The following strings is extracted from the Datalog component with TIdSync.SynchronizeMethod
solution):
status:l77770000140000
status:eFFFF20000140
status:s0000
status:s0000
status:s0000
status:s0000
status:l00005555140000
status:eFFFF20000140
status:s0000
status:s0000
status:s0000
status:s0000
status:l77770000140000
status:eFFFF20000140
But instead I get this result:
status:eFFFF20000140
status:eFFFF20000140
status:s0000
status:s0000
status:s0000
status:s0000
status:s0000
status:s0000
status:s0000
status:s0000
status:s0000
status:l00005555140000
status:l77770000140000
As you see order is not met.
I must get a status:l77770000140000
like packet then a status:eFFFF20000140
an then 4 status:s0000
and so on.
@Remy I've changed your code a little:
TMyNotify = class(TIdNotify)
protected
FBuffer: String;
FMethod: TThreadMethod;
procedure DoNotify; override;
public
constructor Create(const ABuffer: String; AMethod: TThreadMethod);
end;
///......
{ TMyNotify }
constructor TMyNotify.Create(const ABuffer: String; AMethod: TThreadMethod);
begin
inherited Create;
FBuffer := ABuffer;
FMethod := AMethod;
end;
procedure TMyNotify.DoNotify;
begin
inherited;
FMethod;
//Destroy;
end;
And this is how I call ServerExcecute now:
procedure TModules.TCPServerExecute(AContext: TIdContext);
begin
Buffer:= AContext.Connection.IOHandler.ReadLn();
TMyNotify.Create(Buffer, ServerExecute).Notify;
// ServerExecute;
// TIdNotify.NotifyMethod( ServerExecute );
// TIdSync.SynchronizeMethod( ServerExecute );
end;
Upvotes: 2
Views: 934
Reputation: 597036
TIdNotify
is asynchronous. If you receive a new line and overwrite your Buffer
variable before a previous line has a chance to be processed, the previous line will be lost. Change your code to pass the line value inside the TIdNotify
itself, eg:
type
TMyDataProc = procedure(const ABuffer: String) of object;
TMyNotify = class(TIdNotify)
protected
fBuffer: String;
fProc: TMyDataProc;
procedure DoNotify; override;
public
constructor Create(const ABuffer: String; AProc: TMyDataProc);
end;
constructor TMyNotify.Create(const ABuffer: String; AProc: TMyDataProc);
begin
inherited Create;
fBuffer := ABuffer;
fProc := AProc;
end;
procedure TMyNotify.DoNotify;
begin
fProc(fBuffer);
end;
procedure TModules.TCPServerExecute(AContext: TIdContext);
var
LBuffer: String;
begin
LBuffer := AContext.Connection.IOHandler.ReadLn();
TMyNotify.Create(LBuffer, ServerExecute).Notify();
end;
procedure TModules.ServerExecute(const ABuffer: String);
begin
// use ABuffer as needed...
end;
Upvotes: 4