Search code examples
multithreadinglistdelphiobjecttobject

Threadlist of TObject with Delphi - How to populate?


From my limited knowledge about this subject, the following code should work. But I have not the expected result:

type
  TClient = class(TObject)
    Host: String;
  end;

var
  Clients: TThreadList;

const
  Hosts: Array[0..5] of String = ('HOST1', 'HOST2', 'HOST3', 'HOST4', 'HOST5', 'HOST6');
var
  I: Integer;
  List: TList;
  Client: TClient;
begin
  try
    for I := Low(Hosts) to High(Hosts) do
    begin
      Client := TClient.Create;
      with Client Do
      try
        Host := Hosts[I];
        List := Clients.LockList;
        try
          Clients.Add(Client);
        finally
          Clients.UnlockList;
        end;
      finally
        Client.Free;
      end;
    end;
  except
    on E:Exception Do ShowMessage(E.Message);
  end;

// RESULT TEST
List := Clients.LockList;
try
  I := List.Count;
  S := TClient(List.Items[0]).Host;
finally
  Clients.UnlockList;
end;
ShowMessage(IntToStr(I));
ShowMessage(S);

My expected result would be 6 and HOST1, but I got 1 and "" (empty)

Please, what I am missing?

Thanks!


Solution

  • List := Clients.LockList;
    try
      Clients.Add(Client); // <--- mistake here
    finally
      Clients.UnlockList;
    end;
    

    The idiom is that you lock the list with a call to LockList and that returns a mutable list. So you need to call Add on List.

    List := Clients.LockList;
    try
      List.Add(Client);
    finally
      Clients.UnlockList;
    end;
    

    That said, TThreadList does offer an Add method that internally uses LockList. The reason that your call to that Add was failing is that you have used the default value of Duplicates which is dupIgnore. And you were passing the same memory address each time.

    Why was the memory address the same each time? Well, the other mistake you made was to destroy your TClient objects and refer to them later. I guess the memory manager was re-using the memory that you just deallocated.

    You might want to set Duplicates to dupAccept. At the very least you need to be aware that it has a potential impact.

    This program produces your desired output:

    {$APPTYPE CONSOLE}
    
    uses
      SysUtils, Classes;
    
    type
      TClient = class(TObject)
        Host: String;
      end;
    
    const
      Hosts: Array[0..5] of String = ('HOST1', 'HOST2', 'HOST3', 'HOST4', 
        'HOST5', 'HOST6');
    var
      I: Integer;
      List: TList;
      Client: TClient;
      Clients: TThreadList;
    begin
      Clients := TThreadList.Create;
      Clients.Duplicates := dupAccept;
    
      for I := Low(Hosts) to High(Hosts) do
      begin
        Client := TClient.Create;
        Client.Host := Hosts[I];
        List := Clients.LockList;
        try
          List.Add(Client);
        finally
          Clients.UnlockList;
        end;
      end;
    
      List := Clients.LockList;
      try
        Writeln(List.Count);
        Writeln(TClient(List.Items[0]).Host);
      finally
        Clients.UnlockList;
      end;
    end.
    

    Or the loop could be simplified even further:

    for I := Low(Hosts) to High(Hosts) do
    begin
      Client := TClient.Create;
      Client.Host := Hosts[I];
      Clients.Add(Client);
    end;
    

    I neglected to perform any deallocations for the sake of a simpler exposition. Obviously in real code you wouldn't leak the way this code does.

    Personally I'm not a fan of this class. Not in this age of generics. You really should be looking at TThreadList<T>.

    {$APPTYPE CONSOLE}
    
    uses
      SysUtils, Classes, Generics.Collections;
    
    type
      TClient = class
        Host: string;
        constructor Create(AHost: string);
      end;
    
    constructor TClient.Create(AHost: string);
    begin
      inherited Create;
      Host := AHost;
    end;
    
    const
      Hosts: array[0..5] of string = ('HOST1', 'HOST2', 'HOST3', 'HOST4',
        'HOST5', 'HOST6');
    
    var
      Host: string;
      List: TList<TClient>;
      Clients: TThreadList<TClient>;
    
    begin
      Clients := TThreadList<TClient>.Create;
      Clients.Duplicates := dupAccept;
    
      for Host in Hosts do
        Clients.Add(TClient.Create(Host));
    
      List := Clients.LockList;
      try
        Writeln(List.Count);
        Writeln(List.First.Host);
      finally
        Clients.UnlockList;
      end;
    end.