Newsgroups : Borland : borland.public.delphi.internet.winsock : 2006 Jun : Re: Nailed it!
| Subject: | Re: Nailed it! |
| Posted by: | "Remy Lebeau (TeamB)" (no.spam@no.spam.com) |
| Date: | Mon, 26 Jun 2006 11:48:50 |
"Martin James" <mjames_falcon@dial.pipex.com> wrote in message
news:449d268a@newsgroups.borland.com...
> All hard-sync inter-thread comms,, (eg. TThread.synchronize), where
> the sender has to wait for the receiver, invites deadlocks. To avoid
this,
> and improve performance, use queued comms. When communicating to
> the main thread from a secondary thread, this means PostMessage.
You also have to make sure that the main thread processes all of the queued
messages, or else memory can be leaked. Don't just exit the message queue
and terminate the threads, without also checking for any last-minute
messages that may have been queued after the threads were signalled to
terminate but before they actually did so.
> When the message is received in a main-thread message-handler, it
> contains an object with data that has been 'abandoned' by the read
> thread and so can use the data easily.
A safer way would be to copy the data into a thread-safe container that the
main thread owns, such as a TThreadList, and then the worker thread can
signal the main thread when data has been placed into it. When the main
thread receives the message, it can process all of the data that is
currently in the list. This way, the main thread can process multiple data
blocks quickly since it does not have to serialize them one at a time. For
example:
TDataBuffer = class
public
constructor Create(const AData: String);
Field1: string;
Field2: string;
end;
constructor TDataBuffer.Create(const AData: String);
begin
// parse AData into fields as needed ...
end;
TMyForm = class(TForm)
private
procedure WMOnData(var Message: TMessage); message WMONDATA;
//...
public
DataBuffer: TThreadList;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
constructor TMyForm.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
DataBuffer := TThreadList.Create;
end;
destructor TMyForm.Destroy;
var
List: TList;
I: Integer;
begin
// terminate all threads, then ...
List := DataBuffer.LockList;
try
for I := 0 to List.Count-1 do
TDataBuffer(List[I]).Free;
finally
DataBuffer.UnlockList;
DataBuffer.Free;
end;
inherited Destroy;
end;
procedure TMyForm.WMOnData(var Message: TMessage);
var
List: TList;
Data: TDataBuffer;
begin
List := DataBuffer.LockList;
try
while List.Count > 0 do
begin
Data := TDataBuffer(List[0]);
List.Delete(0);
try
try
Memo1.Lines.Add(Data.Field1 + ', ' + Data.Field2);
finally
Data.Free;
end;
except
end;
end;
finally
DataBuffer.UnlockList;
end;
end;
procedure TReadThread.Execute;
var
Buffer: TDataBuffer;
Data: String;
begin
while not Terminated do
begin
Data := fRunSocket.ReadString(fSocket.ReadInteger(False));
Buffer := TDataBuffer.Create(Data);
try
MyForm.DataBuffer.Add(Buffer);
except
Buffer.Free;
raise;
end;
PostMessage(FFormHandle, WM_ONDATA, 0, 0);
end;
end;
> This method is safer than synchronize - will not deadlock.
> This method is faster than synchronize - the read thread does not
> have to wait for the main thread before starting the next read.
You have to be careful that the main thread does not slow down too much, or
else you can push data into the queue faster than it can be processed, so
the queue will fill up over time, will will lose messages, and thus will
also cause memory leaks when PostMessage() fails but the reading thread is
not cleaning up after itself. Using a thread-safe container is safer in
that regard, as the data will not be leaked or lost, albeit in the case of
lost messages and overflowing queues, the data may not be processed for
awhile, but it will eventually.
Gambit