Newsgroups : Borland : borland.public.delphi.internet.winsock : 2007 Jan : Re: AThreads and Thread manager..

www.cryer.info
Managed Newsgroup Archive

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

Replies:

In response to:

www.cryer.info
Managed Newsgroup Archive