Delphi 2006控制台应用程序中的TThread是否有不同的工作方式?

6

我们有一个相当成熟的COM dll,使用DUnit进行测试。我们最近的一个测试创建了一些线程,并从这些线程中测试对象。当使用gui前端运行测试时,此测试正常工作,但在作为控制台应用程序运行时会挂起。以下是我们在测试中拥有的内容的快速伪视图:

SetupTest;
fThreadRefCount := 0; //number of active threads
Thread1 := TMyThread.Create(True);
Inc(fThreadRefCount);
Thread1.OnTerminate := HandleTerminate; //HandleOnTerminate decrements fThreadRefCount
Thread3 := TMyThread.Create(True);
Inc(fThreadRefCount);
Thread2.OnTerminate := HandleTerminate; //HandleOnTerminate decrements fThreadRefCount
Thread3 := TMyThread.Create(True);
Inc(fThreadRefCount);
Thread3.OnTerminate := HandleTerminate; //HandleOnTerminate decrements fThreadRefCount

Thread1.Resume;
Thread2.Resume;
Thread3.Resume;

while fThreadRefCount > 0 do
  Application.ProcessMessages;

我尝试在OnExecute中什么都不做,所以我确定我测试的不是实际代码。在控制台中,fThreadRefCount从未减少,而如果我将其作为GUI应用程序运行,则正常!

就我所知,OnTerminate事件根本没有被调用。


哪个版本的Delphi? - JosephStyons
修订历史显示标题已更改;现在很清楚,谢谢。 - JosephStyons
你能将你的示例简化到最小限度,以便仍然可以编译并显示问题吗?你说Synchronize绝对没有被调用,你是如何验证的 - 通过设置断点吗?如果是这样,请在TMyThread.Execute中设置断点并跟踪为什么没有被调用? - Barry Kelly
好的,我会尝试在这个周末花些时间来做,并看看我能达到什么程度。 - Steve
2个回答

10

您需要提供更多数据。

请注意,OnTerminate 通过 Synchronize() 调用,这需要在某个时刻执行调用 CheckSynchronize()。通常情况下,Application.ProcessMessages() 可以实现此操作,但是根据 VCL 的初始化方式,可能在控制台应用程序中没有完全连接 Synchronize() 机制。

无论如何,在我的计算机上,此程序都按预期工作:

uses Windows, SysUtils, Classes, Forms;

var
  threadCount: Integer;

type
  TMyThread = class(TThread)
  public
    procedure Execute; override;
    class procedure Go;
    class procedure HandleOnTerminate(Sender: TObject);
  end;
  
procedure TMyThread.Execute;
begin
end;

class procedure TMyThread.Go;
  function MakeThread: TThread;
  begin
    Result := TMyThread.Create(True);
    Inc(threadCount);
    Result.OnTerminate := HandleOnTerminate;
  end;
var
  t1, t2, t3: TThread;
begin
  t1 := MakeThread;
  t2 := MakeThread;
  t3 := MakeThread;
  t1.Resume;
  t2.Resume;
  t3.Resume;
  while threadCount > 0 do
    Application.ProcessMessages;
end;

class procedure TMyThread.HandleOnTerminate(Sender: TObject);
begin
  InterlockedDecrement(threadCount);
end;

begin
  try
    TMyThread.Go;
  except
    on e: Exception do
      Writeln(e.Message);
  end;
end.

这只是一个简单的控制台应用程序。它绝对就像你所描述的那样,同步未被调用。不确定我还能提供什么更多的信息。 - Steve
我进一步研究了这个问题,当我将测试编译为控制台应用程序时,checkSyncronize没有被调用。如果我将processmessages调用更改为HandleMessage,则checkSynchronize会被调用,但仅在我从应用程序中单击并返回(onidle处理)时才会被调用。 - Steve

5
正如Barry所指出的那样,除非调用了CheckSyncronize(),否则Synchronize()不会被处理,如果Synchronize()没有被处理,那么OnTerminate事件也不会触发。
当我将单元测试作为控制台应用运行时,似乎发生了这种情况:消息队列中没有消息,因此从Application.ProcessMessages()调用的Application.ProcessMessage()无法调用CheckSynchronize()
现在我通过将循环更改为以下内容来解决这个问题:
While fThreadRefCount > 0 do
begin
   Application.ProcessMessages;
   CheckSynchronize;
end;

现在它可以在控制台模式和图形界面模式下工作。

整个WakeupMainThread挂钩似乎已经正确设置。这个挂钩会发布WM_NULL消息,从而触发CheckSynchronize()。但是在控制台应用程序中,它并没有执行到那一步。

进一步调查

所以,Synchronize()确实被调用了。DoTerminate()调用了Synchronize(CallOnTerminate),但其中有一行代码:

WaitForSingleObject(SyncProcPtr.Signal, Infinite); 

这段代码会一直等待,不会停止。

所以,虽然我上面的修复方法有效,但问题更深层次!


非常感谢您的这篇文章。我整晚都在处理一个线程控制台应用程序,但无法终止线程。它会在clases.pas中的WaitForSingleObject行处停顿并冻结。现在代码已经可以工作了,我可以休息了。再次感谢您在网站上发布这篇文章。 - MikeJ

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