Thread protecting a list of objects - multithreading

I've been working with some multi-threaded applications, and part of this requires thread-protecting objects. I have individual object thread protection down by using the following method:
type
TMyClass = class(TObject)
private
FLock: TRTLCriticalSection;
FSomeString: String;
procedure Lock;
procedure Unlock;
function GetSomeString: String;
procedure SetSomeString(Value: String);
public
constructor Create;
destructor Destroy; override;
property SomeString: String read GetSomeString write SetSomeString;
end;
implementation
constructor TMyClass.Create;
begin
InitializeCriticalSection(FLock);
Lock;
try
//Initialize some stuff
finally
Unlock;
end;
end;
destructor TMyClass.Destroy;
begin
Lock;
try
//Finalize some stuff
finally
Unlock;
end;
DeleteCriticalSection(FLock);
inherited Destroy;
end;
procedure TMyClass.Lock;
begin
EnterCriticalSection(FLock);
end;
procedure TMyClass.Unlock;
begin
LeaveCriticalSection(FLock);
end;
function TMyClass.GetSomeString: String;
begin
Result:= '';
Lock;
try
Result:= FSomeString;
finally
Unlock;
end;
end;
procedure TMyClass.SetSomeString(Value: String);
begin
Lock;
try
FSomeString:= Value;
finally
Unlock;
end;
end;
However, when I implement a list of objects, I can't figure out how to safely protect each object. I create my object lists like this:
type
TMyClass = class;
TMyClasses = class;
TMyClass = class(TObject)
private
FOwner: TMyClasses;
public
constructor Create(AOwner: TMyClasses);
destructor Destroy; override;
end;
TMyClasses = class(TObject)
private
FItems: TList;
function GetMyItem(Index: Integer): TMyItem;
public
constructor Create;
destructor Destroy; override;
procedure Clear;
function Count: Integer;
property Items[Index: Integer]: TMyClass read GetMyItem; default;
end;
implementation
{ TMyClass }
constructor TMyClass.Create(AOwner: TMyClasses);
begin
FOwner:= AOwner;
FOwner.FItems.Add(Self);
//Initialize some stuff...
end;
destructor TMyClass.Destroy;
begin
//Uninitialize some stuff...
inherited Destroy;
end;
{ TMyClasses }
constructor TMyClasses.Create;
begin
FItems:= TList.Create;
end;
destructor TMyClasses.Free;
begin
Clear;
FItems.Free;
inherited Destroy;
end;
procedure TMyClasses.Clear;
begin
while FItems.Count > 0 do begin
TMyClass(FItems[0]).Free;
FItems.Delete(0);
end;
end;
function TMyClasses.Count: Integer;
begin
Result:= FItems.Count;
end;
function TMyClasses.GetMyItem(Index: Integer): TMyClass;
begin
Result:= TMyClass(FItems[Index]);
end;
There are two ways I see doing this, and both ways I don't trust. One way would be to implement a critical section lock in the list object (TMyClasses) and each object within would share this lock (by calling FOwner.Lock; and FOwner.Unlock;. But then two different threads wouldn't even be able to work with two different objects from this list at one time, and would defeat the purpose of multithreading. The second way would be to put another critical section in each individual object of their own, but too many of these is also dangerous, right? How can I protect the list and every object in the list together?

You cannot realistically expect to use the same approach in your list class as you use in the simple class that serializes access to a single object.
For example, your list class has, like so many before it, a Count property, and an indexed Items[] property. I'm going to presume that your threading model allows the list to mutate. Now, suppose you want to write code like this:
for i := 0 to List.Count-1 do
List[i].Frob;
Suppose that another thread were to mutate the list whilst this loop was running. Well, that would clearly lead to runtime failures. So, we can conclude that the loop above would need to be wrapped with a lock. Which means that thread-safety aspects of the list must be exposed externally. You cannot keep it all internal with the current design.
If you wish to keep the lock internal to the class you'll have to remove the Count and Items[] properties. You could have your list looking like this (with some parts removed):
type
TThreadsafeList<T> = class
private
FList: TList<T>;
procedure Lock;
procedure Unlock
public
procedure Walk(const Visit: TProc<T>);
end;
....
procedure TThreadsafeList<T>.Walk(const Visit: TProc<T>);
var
Item: T;
begin
Lock;
try
for Item in FList do
Visit(Item);
finally
Unlock;
end;
end;
And now you can replace the loop above with this:
ThreadsafeList.Walk(
procedure(Item: TMyItemClass)
begin
Item.Frob;
end
);
It's not difficult to extend this concept to allow for your Walk method to support deletion of certain items, as determined by the Visit procedure.
But as you say, quite what you can do with such a list is moot. Shared data is the bane of multi-threading. I suggest you find a way to solve your problem that gives each thread its own private copy of all data that it needs. At which point you need no synchronisation and it's all good.
One final point. There is no single concept of thread safety. What is meant by thread safety varies from context to context. Eric Lippert said it best: What is this thing you call "thread safe"? So anytime you ask a question like this, you should give plenty of detail on your particular use case and threading model.

Related

Delphi set thread to nil after starting it

Today I have missed a lesson and I have found a really weird line of code that I cannot understand. This is the class:
type
TMemoMessageThread = class(TThread)
strict private
FMemo: TMemo;
FMemoMessage: string;
protected
procedure Execute; override;
public
constructor Create(aMemo: TMemo);
property MemoMessage: string read FMemoMessage write FMemoMessage;
end;
Here I have created a class descending from TThread because I want to simulate a heavy computation. Look at the very simple UI.
You click the Button2"Create suspended" which creates a Suspended thread and then use this thread pressing Button3. This is the code:
constructor TMemoMessageThread.Create(aMemo: TMemo);
begin
if (aMemo = nil) then
raise Exception.Create('tMemo non valid!!!');
inherited Create(True);
FreeOnTerminate := True;
FMemo := aMemo;
end;
procedure TMemoMessageThread.Execute;
begin
Synchronize(procedure
begin
FMemo.Lines.Add('... process in parallel thread ...');
end);
Sleep(7000); //simulate something heavy to compute
Synchronize(procedure
begin
FMemo.Lines.Add(FMemoMessage + '. Done! :)');
end);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
MemoMessageThread := TMemoMessageThread.Create(Memo1);
MemoMessageThread.MemoMessage := 'Hello from TMemoMessageThread';
Button3.Enabled := true;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
MemoMessageThread.Start;
MemoMessageThread := nil; //why nil after start?
end;
As you can see, why is there a nil after the start? I really cannot understand. Since it is FreeOnTerminate shouldn't I ignore that line?
Setting MemoMessageThread to nil removes the reference to the instantiated object. The object still lives and executes, but the contact to the object is lost.
Since the thread is created with FreeOnTerminate, you should not use a reference to the object anyway. This is the only purpose of setting it to nil as I can see.
In the scope of this limited example, there indeed is no point in nil-ling the reference. It doesn't do any harm either though.
The thread is created with FreeOnTerminate set, which means that once it has started, you must not access any of its properties again, because the thread might have already been terminated and freed. Nil-ling the reference makes sure that you cannot do that, and as such is not a bad idea, but isn't necessary as long as you remember to not access the started thread instance.

Access Violation on use OleVariant and TWebBrowser in a Thread

i try control a TWebBrowser by OleVariant in a Thread, but i get Access Violation Error. The error only occurs when I use the following code in Delphi XE6:
var
Elements: OleVariant;
begin
Elements := Criar.Web.OleObject.document.all;
end;
Being that "Criar" is a Thread.
See the full code:
type
TCriarWeb = class(TThread)
protected procedure Execute; override;
public
Web: TWebBrowser;
end;
type
TNavegar = class(TThread)
protected procedure Execute; override;
public
end;
procedure TNavegar.Execute;
var
Criar: TCriarWeb;
Elements: OleVariant;
i: Integer;
begin
inherited;
Criar := TCriarWeb.Create;
Sleep(500);
for i := 0 to 100 do begin
Criar.Web.Navigate('http://www.google.com');
while Criar.Web.ReadyState <> READYSTATE_COMPLETE do
Sleep(100);
Elements := Criar.Web.OleObject.document.all;
end;
end;
{ TCriarWeb }
procedure TCriarWeb.Execute;
begin
inherited;
CoInitialize(nil);
Web := TWebBrowser.Create(nil);
Web.HandleNeeded;
Web.Silent := true;
while true do
Application.ProcessMessages;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Navegar: TNavegar;
begin
Navegar := TNavegar.Create;
end;
Follow the link to download the sources: https://www.dropbox.com/s/di3oou8a7ztg22m/Tentativa%20Webbrowser.rar?dl=0
I need a help for resolve this problem. Thanks
You have two major problems that I can see:
You are using COM without initializing it in TNavegar.Execute. In order to access a COM object you do need to initialize COM.
You are creating the COM object in one thread but then using it from another thread. I don't think that is going to work out for this COM object.
Keep all the access of the web browser COM object to the same thread. And once you do that you'll probably also find that you no longer need to include that extremely dubious call to Sleep. My guess is that is there to give the other thread a chance to get going and create the COM object. Any time you find yourself dealing with a threading race, Sleep is not the answer.
My guess is that you don't actually need two threads here and that one will suffice. I also wonder whether or not you really need a web browser control. Can't you do this using a simple HTTP transfer? This might very well allow you to stop calling ProcessMessages, another function that, by and large, should never be called.

Creating/Using FileStream Thread Safe

In my Application when I write text files (logs, traces, etc), I use TFileStream class.
There are cases that I write the data in multithreaded environment, those are the steps:
1- Write Cache Data
2- For each 1000 lines I save to File.
3- Clear Data.
This process is repeated during all processing.
Problem Description:
With 16 threads, the system throws the following exception:
Access Violation - file already in use by another application.
I guess this is happening because that the handle used by one thread is not closed yet, when another thread needs to open.
I changed the architecture to the following: (bellow is the NEW implementation)
In the previous way, the TFileStream was created with FileName and Mode parameters, and destroyed closing the handle (I wasn't using TMyFileStream)
TMyFileStream = class(TFileStream)
public
destructor Destroy; override;
end;
TLog = class(TStringList)
private
FFileHandle: Integer;
FirstTime: Boolean;
FName: String;
protected
procedure Flush;
constructor Create;
destructor Destroy;
end;
destructor TMyFileStream.Destroy;
begin
//Do Not Close the Handle, yet!
FHandle := -1;
inherited Destroy;
end;
procedure TLog.Flush;
var
StrBuf: PChar; LogFile: string;
F: TFileStream;
InternalHandle: Cardinal;
begin
if (Text <> '') then
begin
LogFile:= GetDir() + FName + '.txt';
ForceDirectories(ExtractFilePath(LogFile));
if FFileHandle < 0 then
begin
if FirstTime then
FirstTime := False;
if FileExists(LogFile) then
if not SysUtils.DeleteFile(LogFile) then
RaiseLastOSError;
InternalHandle := CreateFile(PChar(LogFile), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ, nil, CREATE_NEW, 0,0);
if InternalHandle = INVALID_HANDLE_VALUE then
RaiseLastOSError
else if GetLastError = ERROR_ALREADY_EXISTS then
begin
InternalHandle := CreateFile(PChar(LogFile), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ, nil, OPEN_EXISTING, 0,0);
if InternalHandle = INVALID_HANDLE_VALUE then
RaiseLastOSError
else
FFileHandle := InternalHandle;
end
else
FFileHandle := InternalHandle;
end;
F := TMyFileStream.Create(FFileHandle);
try
StrBuf := PChar(Text);
F.Position := F.Size;
F.Write(StrBuf^, StrLen(StrBuf));
finally
F.Free();
end;
Clear;
end;
end;
destructor TLog.Destroy;
begin
FUserList:= nil;
Flush;
if FFileHandle >= 0 then
CloseHandle(FFileHandle);
inherited;
end;
constructor TLog.Create;
begin
inherited;
FirstTime := True;
FFileHandle := -1;
end;
There is another better way?
Is this implementation correct?
May I improve this?
My guess about the Handle was right?
All theads use the same Log object.
There is no reentrance, i checked! there is something wrong with the TFileStream.
The Access to the Add is synchronized, I mean, I used critical session, and when it reaches 1000 lines, Flush procedure is called.
P.S: I do not want third-party component, i want to create my own.
Well, for a start, there's no point in TMyFileStream. What you are looking for is THandleStream. That class allows you to supply a file handle whose lifetime you control. And if you use THandleStream you'll be able to avoid the rather nasty hacks of your variant. That said, why are you even bothering with a stream? Replace the code that creates and uses the stream with a call to SetFilePointer to seek to the end of the file, and a call to WriteFile to write content.
However, even using that, your proposed solution requires further synchronization. A single windows file handle cannot be used concurrently from multiple threads without synchronisation. You hint in a comment (should be in the question) that you are serializing file writes. If so then you are just fine.
The threaded solution provided by Marko Paunovic quite nice, however while reviewing the code I noticed a small mistake, perhaps just an oversight in the example but I thought I'd mention it just the same in case someone actually tries to use it as-is.
There is a missing call to Flush in TLogger.Destroy, as a result any unflushed (buffered) data is disgarded when the TLogger object is destroyed.
destructor TLogger.Destroy;
begin
if FStrings.Count > 0 then
Flush;
FStrings.Free;
DeleteCriticalSection(FLock);
inherited;
end;
How about:
In each thread, add log lines to a TStringList instance until lines.count=1000. Then push the TStringList onto a blocking producer-consumer queue, immediately create a new TStringList and carry on logging to the new list.
Use one Logging thread that dequeues the TStringList instances, writes them to the file and then frees them.
This isolates the log writes from disk/network delays, removes any reliance on dodgy file-locking and will actually work reliably.
I figured MY MISTAKE.
In first place, I want to apologize for posting this stupid question without a proper way to reproduce the exception. In other words, without a SSCCE.
The problem was a control flag that my TLog class used internally.
This flag was created, when we started to evolve our product a parallel architecture.
As we needed to keep the previous form working (at least until everything was in the new architecture).
We created some flags to identify if the object was either the new or old version.
One of that flags was named CheckMaxSize.
If CheckMaxSize was enabled, at a certain moment, every data inside the instance of this object in each thread, would be thrown to the main instance, which was in the "main" thread (not the GUI one, because it was a background work). Furthermore, when CheckMaxSize is enabled, TLog should never ever call "flush".
Finally, as you can see, in TLog.Destroy there is no check to CheckMaxSize. Therefore, the problem would happen because the name of the file created by this class was always the same, since it was processing the same task, and when One object created the file and another one tried to create another file with the same name, inside the same folder, the OS (Windows) rose an Exception.
Solution:
Rewrite the destructor to:
destructor TLog.Destroy;
begin
if CheckMaxSize then
Flush;
if FFileHandle >= 0 then
CloseHandle(FFileHandle);
inherited;
end;
If you have multithreaded code that needs to write to single file, it's best to have as much control as you can in your hands. And that means, avoid classes which you are not 100% sure how they work.
I suggest that you use multiple threads > single logger architecture, where each thread will have reference to logger object, and add strings to it. Once 1000 lines are reached, logger would flush the collected data in file.
There is no need to use TFileStream to write data to file, you can
go with CreateFile()/SetFilePointer()/WriteFile(), as David already suggested
TStringList is not thread-safe, so you have to use locks on it
main.dpr:
{$APPTYPE CONSOLE}
uses
uLogger,
uWorker;
const
WORKER_COUNT = 16;
var
worker: array[0..WORKER_COUNT - 1] of TWorker;
logger: TLogger;
C1 : Integer;
begin
Write('Creating logger...');
logger := TLogger.Create('test.txt');
try
WriteLn(' OK');
Write('Creating threads...');
for C1 := Low(worker) to High(worker) do
begin
worker[C1] := TWorker.Create(logger);
worker[C1].Start;
end;
WriteLn(' OK');
Write('Press ENTER to terminate...');
ReadLn;
Write('Destroying threads...');
for C1 := Low(worker) to High(worker) do
begin
worker[C1].Terminate;
worker[C1].WaitFor;
worker[C1].Free;
end;
WriteLn(' OK');
finally
Write('Destroying logger...');
logger.Free;
WriteLn(' OK');
end;
end.
uWorker.pas:
unit uWorker;
interface
uses
System.Classes, uLogger;
type
TWorker = class(TThread)
private
FLogger: TLogger;
protected
procedure Execute; override;
public
constructor Create(const ALogger: TLogger);
destructor Destroy; override;
end;
implementation
function RandomStr: String;
var
C1: Integer;
begin
result := '';
for C1 := 10 to 20 + Random(50) do
result := result + Chr(Random(91) + 32);
end;
constructor TWorker.Create(const ALogger: TLogger);
begin
inherited Create(TRUE);
FLogger := ALogger;
end;
destructor TWorker.Destroy;
begin
inherited;
end;
procedure TWorker.Execute;
begin
while not Terminated do
FLogger.Add(RandomStr);
end;
end.
uLogger.pas:
unit uLogger;
interface
uses
Winapi.Windows, System.Classes;
type
TLogger = class
private
FStrings : TStringList;
FFileName : String;
FFlushThreshhold: Integer;
FLock : TRTLCriticalSection;
procedure LockList;
procedure UnlockList;
procedure Flush;
public
constructor Create(const AFile: String; const AFlushThreshhold: Integer = 1000);
destructor Destroy; override;
procedure Add(const AString: String);
property FlushThreshhold: Integer read FFlushThreshhold write FFlushThreshhold;
end;
implementation
uses
System.SysUtils;
constructor TLogger.Create(const AFile: String; const AFlushThreshhold: Integer = 1000);
begin
FFileName := AFile;
FFlushThreshhold := AFlushThreshhold;
FStrings := TStringList.Create;
InitializeCriticalSection(FLock);
end;
destructor TLogger.Destroy;
begin
FStrings.Free;
DeleteCriticalSection(FLock);
inherited;
end;
procedure TLogger.LockList;
begin
EnterCriticalSection(FLock);
end;
procedure TLogger.UnlockList;
begin
LeaveCriticalSection(FLock);
end;
procedure TLogger.Add(const AString: String);
begin
LockList;
try
FStrings.Add(AString);
if FStrings.Count >= FFlushThreshhold then
Flush;
finally
UnlockList;
end;
end;
procedure TLogger.Flush;
var
strbuf : PChar;
hFile : THandle;
bWritten: DWORD;
begin
hFile := CreateFile(PChar(FFileName), GENERIC_WRITE, FILE_SHARE_READ, nil, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
try
strbuf := PChar(FStrings.Text);
SetFilePointer(hFile, 0, nil, FILE_END);
WriteFile(hFile, strbuf^, StrLen(strbuf), bWritten, nil);
FStrings.Clear;
finally
CloseHandle(hFile);
end;
end;
end.

Delphi2006 - Is there TList with TMultiReadExclusiveWriteSynchronizer?

I have external application that is multithreaded and this app is using my custom dll to do some things from that threads.
In this dll I have 2 functions that read from and write some data to TList.
I need that list to be freely read by those threads but only one can write at a time, the rest must wait for their time to write.
My question:
- is there in BDS 2006 a TList component that have TMREWSync capability or
- maybe You know any free third-party component that I can use in my app or
- maybe You have some customized TList code that can do things like this mentioned above.
Edit:
I need something like TThreadList.LockList but only for writing to that list.
Thanks for any help.
It's simple enough to put together a TMultiReadExclusiveWriteSynchronizer and TList in the same manner as TThreadList. If you already know how these classes work, then you'll be able to follow the code below.
type
TReadOnlyList = class
private
FList: TList;
function GetCount: Integer;
function GetItem(Index: Integer): Pointer;
public
constructor Create(List: TList);
property Count: Integer read GetCount;
property Items[Index: Integer]: Pointer read GetItem;
end;
TMREWList = class
private
FList: TList;
FReadOnlyList: TReadOnlyList;
FLock: TMultiReadExclusiveWriteSynchronizer;
public
constructor Create;
destructor Destroy; override;
function LockListWrite: TList;
procedure UnlockListWrite;
function LockListRead: TReadOnlyList;
procedure UnlockListRead;
end;
{ TReadOnlyList }
constructor TReadOnlyList.Create(List: TList);
begin
inherited Create;
FList := List;
end;
function TReadOnlyList.GetCount: Integer;
begin
Result := FList.Count;
end;
function TReadOnlyList.GetItem(Index: Integer): Pointer;
begin
Result := FList[Index];
end;
{ TMREWList }
constructor TMREWList.Create;
begin
inherited;
FList := TList.Create;
FReadOnlyList := TReadOnlyList.Create(FList);
FLock := TMultiReadExclusiveWriteSynchronizer.Create;
end;
destructor TMREWList.Destroy;
begin
FLock.Free;
FReadOnlyList.Free;
FList.Free;
inherited;
end;
function TMREWList.LockListWrite: TList;
begin
FLock.BeginWrite;
Result := FList;
end;
procedure TMREWList.UnlockListWrite;
begin
FLock.EndWrite;
end;
function TMREWList.LockListRead: TReadOnlyList;
begin
FLock.BeginRead;
Result := FReadOnlyList;
end;
procedure TMREWList.UnlockListRead;
begin
FLock.EndRead;
end;
This is the most basic implementation possible. If you wish you could add some more bells and whistles in the manner of TThreadList.

Delphi 7 + Indy + Multithread clients

My PC is connected to two electronic devices that send data using a TCP connection. I would like to develop a Delphi program able to log all this data. In the following code I create two TCPClients using two threads. I am able to log data from one device using one thread, but when two threads are runnning the application freezes. I donĀ“t have experience programming in Delphi, sorry if there are too many mistakes... I am using Delphi 7. What can I do to avoid freezing the app?
Thanks in advance
//************************************************************************//
type TThreadConn1 = class(TThread)
private
protected
procedure Execute; override;
end;
type TThreadConn2 = class(TThread)
private
protected
procedure Execute; override;
end;
var
Form1: TForm1;
TCP1: TThreadConn1;
TCP2: TThreadConn2;
flag1: bool;
flag2: bool;
implementation
{$R *.dfm}
//******************************Connection 1******************************//
procedure TThreadConn1.Execute; //Connect+loop read buffer+disconnect
begin
Form1.IdTCPClient1.Connect;
While flag1 = false do
Form1.IdTCPClient1.CurrentReadBuffer;
Form1.IdTCPClient1.Disconnect;
end;
procedure TForm1.ButtonConnection1Click(Sender: TObject);
begin
flag1:=false;
TCP1 := TThreadConn1.Create(false); //Launch thread
end;
procedure TForm1.ButtonDisconnection1Click(Sender: TObject);
begin
flag1:=true;
if (TCP1.Terminated = false) then
TCP1.Terminate; //Is it ok to finish this way a thread?
end;
//******************************Connection2******************************//
procedure TThreadConn2.Execute; //Connect+loop read buffer+disconnect
begin
Form1.IdTCPClient2.Connect;
While flag2 = false do
Form1.IdTCPClient1.CurrentReadBuffer;
Form1.IdTCPClient2.Disconnect;
end; { of procedure }
procedure TForm1.ButtonConnection2Click(Sender: TObject);
begin
flag2:=false;
TCP2 := TThreadConn2.Create(false);
end;
procedure TForm1.ButtonDisconnection2Click(Sender: TObject);
begin
flag2:=true;
if (TCP2.Terminated = false) then
TCP2.Terminate;
end;
end.
You don't need to create two separate threads that do the same thing. Create one class and then instantiate multiple copies of it. Try this instead:
type
TThreadConn = class(TThread)
private
FClient: TIdTCPClient;
protected
procedure Execute; override;
public
constructor Create(AClient: TIdTCPClient);
end;
var
TCP1: TThreadConn = nil;
TCP2: TThreadConn = nil;
constructor TThreadConn.Create(AClient: TIdTCPClient);
begin
inherited Create(False);
FClient := AClient;
end;
procedure TThreadConn.Execute;
begin
FClient.Connect;
try
while Terminated = false do
FClient.CurrentReadBuffer;
finally
FClient.Disconnect;
end;
end;
procedure TForm1.ButtonConnection1Click(Sender: TObject);
begin
TCP1 := TThreadConn.Create(TIdTCPClient1);
end;
procedure TForm1.ButtonDisconnection1Click(Sender: TObject);
begin
if (TCP1 <> nil) then
begin
TCP1.Terminate;
TCP1.WaitFor;
FreeAndNil(TCP1);
end;
end;
procedure TForm1.ButtonConnection2Click(Sender: TObject);
begin
TCP2 := TThreadConn.Create(IdTCPClient2);
end;
procedure TForm1.ButtonDisconnection2Click(Sender: TObject);
begin
if (TCP2 <> nil) then
begin
TCP2.Terminate;
TCP2.WaitFor;
FreeAndNil(TCP2);
end;
end;
Apart from the overall design, (which you will probably fix in time, maybe with some more help), in TThreadConn2.Execute, you call Form1.IdTCPClient1.CurrentReadBuffer.
Hints:
If you want to use threads to communicate with several different clients, don't plonk TidTCPClients onto forms. This is inflexible because every time you want to add a new client, you have to plonk another instance onto the form and rebuild. It also makes for a lot of extra work to get your app to shut down cleanly which, I assure you, you will not want.
Dynamically create a TidTCPClient instance in either the TThread descendant ctor or at the top of the 'Execute' method.
Try and declare one class that can be instantiated for each client connection, so you don't have to copy/paste code, edit it and and get it wrong
Oh - for now, don't try and terminate the threads at all. In fact, forever, don't try and terminate the threads at all. In the execute, loop around connect() until successful, then read stuff in a loop, write to log, (in a thread-safe manner). Do that forever. If you wnat to stop the logging, set a 'don't log' boolean so that the thread still runs but simply doesn't bother calling the logger. Again - don't go near trying to terminate the threads.
If I get a chance over the weekend, I'll do a simple example - one form, four TEdits for two pair hostname/port and two TMemos to send data to and display data from the server.

Resources