Delphi中将Indy 9升级到Indy 10,TIdSchedulerOfThreadPool初始化

5

我正在将一个Delphi应用从Indy 9更新到Indy 10。

这是一个比较痛苦的过程,因为显然有很多变化。

我卡在了一个步骤上。

以下是旧代码(适用于Indy 9):

创建线程池并初始化每个线程,然后启动它们。每个线程创建一个Indy HTTP客户端(但这里并不重要)。

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;

TIdThreadMgrPool 类已经在 Indy 10 中被取消。

我寻找了一个替代方案,TIdSchedulerOfThreadPool 看起来是一个不错的选择, 但我无法运行它。

以下是修改后(Indy 10)的代码:

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;

我在这里遇到一个访问冲突异常(这是indy代码):

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

FBeforeRunDone为空。

1个回答

7
您说得没错,TIdSchedulerOfThreadPool是Indy 10中TIdThreadMgrPool的替代品。然而,您没有考虑到TIdScheduler的架构与TIdThreadMgr的架构相差很大。
在Indy 10中,TIdThreadWithTask不能单独运行。正如其名称所示,TIdThreadWithTask执行任务,这是一个TIdTask派生的对象(比如TIdContext,它是替代TIdPeerThread的Indy 10对象),与线程相关联。如果没有给线程分配任务,那么就会出现崩溃情况。想要手动调用Start()方法,需要首先创建并分配一个基于TIdTask的对象到TIdThreadWithTask.Task属性上。TIdTCPServer通过调用TIdScheduler.AcquireYarn()来处理,以创建与TIdThreadWithTask对象相关联的TIdYarn对象,然后创建一个TIdContext对象并将其传递给TIdScheduler.StartYarn(),该方法使用TIdYarn访问TIdThreadWithTask并将其Task属性赋值后再调用其Start()方法。
然而,现在并没有什么好办法。无论是Indy 9还是10,您实际上都不应该手动调用TIdThread.Start()。当接受新客户端连接、从ThreadMgr/Scheduler中获取线程,并将客户端连接与线程关联后,TIdTCPServer会为您处理这些。您可以根据需要初始化线程属性,但不会立即运行线程。这些属性将在稍后第一次开始运行线程时生效。
请尝试以下操作:
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;

现在,说完了这些,还有最后一件需要注意的事情。在Indy 9和10中,线程可能无法在完成后放回池中,并且在初始化代码运行后,新线程可能会被添加到池中。PoolSize是保持池中线程数量的最小值,而不是绝对计数。超过PoolSize数量的客户端可以连接到服务器,并且当需要时,它将愉快地为它们创建更多线程,从而绕过您的初始化代码。在两个版本中,初始化线程的最佳位置是TUrlThread构造函数中。在构造函数可以访问它所需时,请将Controler指针存储在某个地方。给每个线程分配一个Index也没有意义,因为池中线程的顺序随时间动态改变。
实际上,您的手动初始化代码在两个版本中都是错误的方法。TIdThreadMgrPool.GetThread()和TIdSchedulerOfThreadPool.NewThread()根本不会将新线程添加到池中。在线程停止运行且有空间保存该线程以供重复使用时,线程才会在Indy 9和10中添加到池中,而在Indy 10中,仅在TIdTCPServer启动时才会添加线程。因此,您实际上正在创建未实际执行任何操作并且未被池跟踪的线程。更加理性的方法是重新设计两个版本的初始化代码,使线程在正常情况下创建时自行初始化,而不是手动干预架构来创建它们。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接