Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Migrating from Indy 9 to 10 with Delphi, TIdSchedulerOfThreadPool initialization

I'm in the process of updating a Delphi app from Indy 9 to Indy 10.

It's quite painful, as apparently a lot has changed.

I'm stuck at one step.

Here is the old code (working with Indy 9):

A Thread Pool is created and every thread of the pool is initialized and then started. The individual threads create an indy http client (but it does not matter here).

TUrlThread = class(TIdThread)

...  

var
  i: Integer;
begin
  // create the Pool and init it
  Pool            := TIdThreadMgrPool.Create(nil);
  Pool.PoolSize   := Options.RunningThreads;
  Pool.ThreadClass:= TUrlThread;

  // init threads and start them
  for i := 1 to Options.RunningThreads do
  begin
    with (Pool.GetThread as TUrlThread) do
    begin
      Index     := i;
      Controler := Self;
      Priority  := Options.Priority;
      Start;
    end;
  end;

The TIdThreadMgrPool class is gone with Indy 10.

I've looked for a replacement and TIdSchedulerOfThreadPool looks like a winner, but I cannot get it running.

Here is the modified (Indy 10) code:

TUrlThread = class(TIdThreadWithTask)

...

var
  i: Integer;
begin
  // create the Pool and init it
  Pool            := TIdSchedulerOfThreadPool.Create(nil);
  Pool.PoolSize   := Options.RunningThreads;
  Pool.ThreadClass:= TUrlThread;

  // init threads and start them
  for i := 1 to Options.RunningThreads do
  begin
    with (Pool.NewThread as TUrlThread) do
    begin
      Index     := i;
      Controler := Self;
      Priority  := Options.Priority;
      Start;
    end;
  end;

I get an access violation exception here (this is indy code):

procedure TIdTask.DoBeforeRun;
begin
  FBeforeRunDone := True;
  BeforeRun;
end;

FBeforeRunDone is nil.

like image 655
Casady Avatar asked Jan 25 '13 03:01

Casady


1 Answers

You are correct that TIdSchedulerOfThreadPool is Indy 10's replacement for TIdThreadMgrPool. However, what you are not taking into account is that the TIdScheduler architecture is quite a bit different than the TIdThreadMgr architecture.

In Indy 10, TIdThreadWithTask does not operate by itself. As its name implies, TIdThreadWithTask performs a Task, which is a TIdTask-derived object (such as TIdContext, which is Indy 10's replacement for TIdPeerThread) that gets associated with the thread. You are running threads without giving them tasks to perform, that is why you are experiencing crashes. In order to call Start() manually, you need to first create and assign a TIdTask-based object to the TIdThreadWithTask.Task property. TIdTCPServer handles that by calling TIdScheduler.AcquireYarn() to create a TIdYarn object that is linked to a TIdThreadWithTask object, then creates a TIdContext object and passes it to TIdScheduler.StartYarn(), which uses the TIdYarn to access the TIdThreadWithTask to assign its Task property before then calling Start() on it.

However, all is not lost. In both Indy 9 and 10, you really should not be calling TIdThread.Start() manually to begin with. TIdTCPServer handles that for you after accepting a new client connection, obtaining a thread from its ThreadMgr/Scheduler, and associating the client connection to the thread. You can initialize your thread properties as needed without actually running the threads immediately. The properties will take effect the first time the threads begin running at a later time.

Try this:

TUrlThread = class(TIdThread)

...  

var
  i: Integer;
begin
  // create the Pool and init it
  Pool            := TIdThreadMgrPool.Create(nil);
  Pool.PoolSize   := Options.RunningThreads;
  Pool.ThreadClass:= TUrlThread;
  Pool.ThreadPriority := Options.Priority;

  // init threads and start them
  for i := 1 to Options.RunningThreads do
  begin
    with (Pool.GetThread as TUrlThread) do
    begin
      Index     := i;
      Controler := Self;
    end;
  end;

.

TUrlThread = class(TIdThreadWithTask)

...

var
  i: Integer;
begin
  // create the Pool and init it
  Pool            := TIdSchedulerOfThreadPool.Create(nil);
  Pool.PoolSize   := Options.RunningThreads;
  Pool.ThreadClass:= TUrlThread;
  Pool.ThreadPriority := Options.Priority;

  // init threads and start them
  for i := 1 to Options.RunningThreads do
  begin
    with (Pool.NewThread as TUrlThread) do
    begin
      Index     := i;
      Controler := Self;
    end;
  end;

Now, with that said, one last thing to watch out for. In both Indy 9 and 10, it is possible for threads to not be put back in the pool when finished, and for new threads to get added to the pool after your initialization code has run. The PoolSize is the minimum number of threads to keep in the pool, not an absolute count. More than PoolSize number of clients can connect to the server and it will happily create more threads for them at the time they are needed, thus bypassing your initialization code. In both versions, the best place to initalize your threads is in the TUrlThread constructor. Store your Controler pointer somewhere that the constructor can reach it when needed. And it does not make sense to assign an Index to each thread since the order of the threads in the pool changes dynamically over time.

In fact, your manual initialization code is actually the wrong approch in both versions for another reason. Both TIdThreadMgrPool.GetThread() and TIdSchedulerOfThreadPool.NewThread() do not add the new thread to the pool at all. Threads are added to the pool in both Indy 9 and 10 when a thread stops running and there is room to save the thread for reuse, and additionally in Indy 10 only when TIdTCPServer is starting up. So you are actually creating threads that are not actually doing anything and are not being tracked by the pool. All the more reason to re-design your initialization code in both versions so threads initialize themselves when they are created under normal conditions, rather than you hacking into the architecture to create them manually.

like image 91
Remy Lebeau Avatar answered Nov 15 '22 07:11

Remy Lebeau