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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With