Newsgroups : Borland : borland.public.delphi.internet.winsock : 2007 Jan : Re: AThreads and Thread manager..
| Subject: | Re: AThreads and Thread manager.. |
| Posted by: | "Remy Lebeau (TeamB)" (no.spam@no.spam.com) |
| Date: | Thu, 4 Jan 2007 15:38:12 |
"Turboz" <turboz@turboz.moved.in> wrote in message
news:459d7e09@newsgroups.borland.com...
> Users: Array of TUser;
That array is not thread-safe. You should use a TThreadList instead.
> If Users[I].Connection.Thread = AThread then
That is exactly what I'm telling you NOT to use. If you must find a
TUser from a TIdPeerThread pointer, then I suggest you put the TUser
pointer into the TIdPeerThread's Data property. Then you don't have
to look up anything at all.
> //When writing to the client...
>
TIdPeerThread(Users[I].Connection.Thread).Connection.WriteLn(TheMessag
e);
Make sure that is thread-safe. Writing to the Connection from a
different thread than the one that is managing the client can cause
packets to overlap if you are not careful. You should wrap the
writing in a critical section to prevent multiple threads writing to
the socket at the same time.
> I'd seen the above method of addressing the AThread used in one of
the
> old Indy 8 demo's by: Jeremy Darling (though admittedly he also used
> Threads.Locklist and client objects which doesn't work for me
because
> I need to track ALL online AND offline users in the same place - EG
> an array.
There is nothing preventing you from doing that with a TThreadList.
Your array/list/whatever must be thread-safe, or else you risk
screwing up your data, i not crashing the code altogether.
> The IdTCPServer threadlist of course only contains those active
> connections which is why I have avoided using it.
It only stores the thread pointers. There is nothing stopping you
from storing your user pointers in your own list as well.
> Yes, but why is that better than simply using the method above to
> match the AThread with a TIdPeerThread ?
Because you are matching up your users with a pointer that is dynamic
and can go away at any time - even while you are looking for it. It
is not a good way to keep track of your users.
> I still don't really see why I must use the data object
You've already said why - because you need to keep track of offline
users. That in itself requires you to have a separate data object for
your user data. It just happens when a user is online, it can be
associated with a thread pointer as well. But that does not negate
that you should not be using the thread pointer to identify your users
in the first place.
> I'm not trying to associate the thread with an object
Yes, you are.
> more like keep a record of which user is associated with which
thread.
In other words, to associate a thread with a [user] object.
> Well I'm basically just trying to keep all my users (both on and
offline)
> in one place - A dynamic array.
Try this instead:
> Not really keen on playing with AThread.Data property to be honest!
You have to, for exactly the reason you just said - it is quick and
makes the code easier to use.
type
TUser = class
public
UserName,
Password,
Email,
Session,
Key,
IP: String;
Thread: TIdPeerThread;
Lock: TCriticalSection;
constructor Create;
destructor Destroy; override;
end;
public
Users: TThreadList;
constructor TUser.Create;
begin
inherited Create;
Lock := TCriticalSection.Create;
end;
destructor TUser.Destroy;
begin
Lock.Free;
inherited Destroy;
end;
constructor TForm1.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Users := TThreadList.Create;
end;
destructor TForm1.Destroy;
var
List: TList;
I: Integer;
begin
IdTCPServer1.Active := False;
List := Users.LockList;
try
for I := 0 to List.Count-1 do
TUser(List[I]).Free;
finally
Users.UnlockList;
end;
Users.Free;
inherited Destroy;
end;
procedure TForm1.IdTCPServer1Connect(AThread: TIdPeerThread);
var
//...
List: TList;
Client, User: TUser;
I: Integer;
begin
// gather information from connecting client ...
Client := nil;
List := Users.LockList;
try
// check if an existing user is reconnecting...
for I := 0 to List.Count-1 do
begin
User := TUser(List[I]);
if (User matches the connecting client) then
begin
Client := User;
Break;
end;
end;
// check if new user is connecting for the first time...
if Client = nil then
begin
Client := TUser.Create;
try
List.Add(Client);
except
Client.Free;
raise;
end;
end;
// update Client info as needed ...
AThread.Data := Client;
Client.Thread := AThread;
finally
Users.UnlockList;
end;
//...
end;
procedure TForm1.IdTCPServer1Disconnect(AThread: TIdPeerThread);
var
List: TList;
Client: TUser;
begin
Client := TUser(AThread.Data);
AThread.Data := nil;
if Client <> nil then
begin
Client.Lock.Enter;
try
Client.Thread := nil;
finally
Client.Lock.Leave;
end;
end;
//...
end;
procedure TForm1.SendToUser(AUser: TUser; const Message: String);
begin
if (AUser = nil) or (Message = '') then Exit;
// ensure the send is not occuring while the thread is being
terminated
AUser.Lock.Enter;
try
if AUser.Thread <> nil then
AUser.Thread.Connection.WriteLn(Message);
finally
AUser.Lock.Leave;
end;
end;
procedure GetThreadUserName(AThread: TIdPeerThread);
begin
if (AThread <> nil) and (AThread.Data <> nil) then
Result := TUser(AThread.Data).UserName
else
Result := '';
end;
Gambit