6

DUnit を使用してテストする、かなり成熟した COM dll があります。最近のテストの 1 つは、いくつかのスレッドを作成し、それらのスレッドからオブジェクトをテストします。このテストは、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 イベントは呼び出されていません。

4

2 に答える 2

8

より多くのデータを提供する必要があります。

OnTerminateはSynchronizeを介して呼び出されることに注意してください。これには、どこかの時点でCheckSynchronizeを呼び出す必要があります。Application.ProcessMessagesは通常これを行いますが、VCLの初期化方法によっては、同期メカニズムがコンソールアプリケーションに完全に接続されていない可能性があります。

いずれにせよ、このプログラムは私のマシンで期待どおりに機能します。

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.
于 2008-11-07T19:07:41.607 に答える
5

バリーが正しく指摘したように、CheckSyncronizeが呼び出されない限り、Synchronizeは呼び出されず、synchronizeが呼び出されない場合、OnTerminateイベントは発生しません。ユニットテストをコンソールアプリケーションとして実行すると、メッセージキューにメッセージがないため、Processmessagesから呼び出されるApplication.ProcessMessageがcheckSynchronizeを呼び出さないことが発生しているようです。ループを次のように変更することで問題を解決しました

While fThreadRefCount > 0 do
begin
   Application.ProcessMessages;
   CheckSynchronize;
end;

これで、コンソールモードとGUIモードの両方で機能します。

wakeupmainthreadフック全体が正しく設定されているようです。checksynchronizeをトリガーするのは、このフックがWM_NULLメッセージを送信することです。コンソールアプリではそれほど遠くはありません。

詳細調査

したがって、Synchronize呼び出されます。DoTerminateはSynchronize(CallOnTerminate)を呼び出しますが、そこに行があります:

WaitForSingleObject(SyncProcPtr.Signal, Infinite); 

それはただ永遠に待つだけです。

したがって、上記の修正は機能しますが、これにはさらに深いことがあります。

于 2008-11-09T15:07:05.273 に答える