3

TThread例外が発生した場合、例外のクラスとメッセージを 2 つのプライベート フィールドに保存する子孫クラスを作成しました。

private
  //...
  FExceptionClass: ExceptClass;  // --> Class of Exception
  FExceptionMessage: String;
  //...

raiseメインスレッドがそれを処理できるように、イベントで同様の例外が発生する可能性があると思いOnTerminateました (ここでは簡略化したバージョンを示します)。

procedure TMyThread.Execute;
begin
  try
    DoSomething;
    raise Exception.Create('Thread Exception!!');
  except
    on E:Exception do
    begin
      FExceptionClass := ExceptClass(E.ClassType);
      FExceptionMessage := E.Message;
    end;
  end;
end;

procedure TMyThread.DoOnTerminate(Sender: TObject);
begin
  if Assigned(FExceptionClass) then
    raise FExceptionClass.Create(FExceptionMessage);
end;

標準の例外処理メカニズム (エラー ダイアログ ボックス) が発生することを期待していますが、さまざまな結果が得られます。例外が発生しなかったかのようにオンになります。
問題はコールスタックに関するものだと思います。
それは悪い考えですか?
スレッドの例外をメインスレッドから分離するが、それらを標準的な方法で再現する別の方法はありますか?
ありがとうございました

4

4 に答える 4

6

私の考えでは、この質問の基本的な問題は次のとおりです。

OnTerminateスレッドのイベント ハンドラーで例外を発生させるとどうなるか。

スレッドのOnTerminateイベント ハンドラは、 への呼び出しによってメイン スレッドで呼び出されますSynchronize。ここで、OnTerminateイベント ハンドラーが例外を発生させています。したがって、その例外がどのように伝播するかを理解する必要があります。

イベント ハンドラのコール スタックを調べるOnTerminateと、メイン スレッドで から呼び出されていることがわかりますCheckSynchronize。関連するコードは次のとおりです。

try
  SyncProc.SyncRec.FMethod; // this ultimately leads to your OnTerminate
except
  SyncProc.SyncRec.FSynchronizeException := AcquireExceptionObject;
end;

したがって、CheckSynchronize例外をキャッチし、FSynchronizeException. その後、実行が続行され、FSynchronizeException後で発生します。そして、隠蔽された例外が で発生していることがわかりましたTThread.Synchronize。の最後の死ぬ行為TThread.Synchronizeは次のとおりです。

if Assigned(ASyncRec.FSynchronizeException) then 
  raise ASyncRec.FSynchronizeException;

これが意味することは、メイン スレッドで発生する例外を取得しようとする試みが、例外をスレッドに戻したフレームワークによって阻止されたことです。raise ASyncRec.FSynchronizeExceptionが実行される時点で、このシナリオではアクティブな例外ハンドラーがないため、これはちょっとした災害です。これは、スレッド プロシージャが SEH 例外をスローすることを意味します。そして、それは家を崩壊させます。

したがって、これらすべてからの私の結論は次のルールです。

      スレッドのOnTerminateイベント ハンドラーで例外を発生させないでください。

メイン スレッドでこのイベントを表示するには、別の方法を見つける必要があります。たとえば、PostMessage.


余談ですが、Executeメソッドに例外ハンドラーを実装する必要はありませんTThread。既に実装されているからです。

の実装は、への呼び出しをtry/except ブロックにTThreadラップします。Executeこれは のThreadProc関数にありClassesます。関連するコードは次のとおりです。

try
  Thread.Execute;
except
  Thread.FFatalException := AcquireExceptionObject;
end;

イベント ハンドラーは例外がキャッチされた後に呼び出されるため、上記で発見したように単純に例外を発生させるのOnTerminateではなく、そこから再表示することを完全に選択できます。

コードは次のようになります。

procedure TMyThread.Execute;
begin
  raise Exception.Create('Thread Exception!!');
end;

procedure TMyThread.DoOnTerminate(Sender: TObject);
begin
  if Assigned(FatalException) and (FatalException is Exception) then
    QueueExceptionToMainThread(Exception(FatalException).Message);
end;

明確にするために、QueueExceptionToMainThreadいくつかの機能を作成する必要があります。

于 2013-07-30T13:02:11.760 に答える
2

例外の同期呼び出しは、スレッドの中断を妨げません。function ThreadProc以降は省略Thread.DoTerminate;されます。

上記のコードには 2 つのテスト ケースがあります

  • コメント付き/コメントなしの同期例外があるもの //**
  • OnTerminate イベントでカプセル化されていない (カプセル化されていない) 例外を持つ 2 番目の例外。

 

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TMyThread=Class(TThread)
    private
      FExceptionClass: ExceptClass;
      FExceptionMessage: String;
    procedure DoOnTerminate(Sender: TObject);
    procedure SynChronizedException;
    procedure SynChronizedMessage;
    public
      procedure Execute;override;
      Destructor Destroy;override;
  End;
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}
procedure TMyThread.SynChronizedException;
begin
  Showmessage('> SynChronizedException');
  raise Exception.Create('Called Synchronized');
  Showmessage('< SynChronizedException'); // will never be seen
end;

procedure TMyThread.SynChronizedMessage;
begin
  Showmessage('After SynChronizedException');
end;

procedure TMyThread.Execute;
begin
  try
    OnTerminate :=  DoOnTerminate;      // first test
    Synchronize(SynChronizedException); //** comment this part for second test
    Synchronize(SynChronizedMessage); // will not be seen
    raise Exception.Create('Thread Exception!!');
  except
    on E:Exception do
    begin
      FExceptionClass := ExceptClass(E.ClassType);
      FExceptionMessage := E.Message;
    end;
  end;
end;

destructor TMyThread.Destroy;
begin
  Showmessage('Destroy ' + BoolToStr(Finished)) ;
  inherited;
end;

procedure TMyThread.DoOnTerminate(Sender: TObject);
begin
  {  with commented part above this will lead to a not called destructor
  if Assigned(FExceptionClass) then
      raise FExceptionClass.Create(FExceptionMessage);
  }
  if Assigned(FExceptionClass) then
      try // just silent for testing
        raise FExceptionClass.Create(FExceptionMessage);
      except
      end;

end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  With TMyThread.Create(false) do FreeOnTerminate := true;
  ShowMessage('Hallo');
end;

end.
于 2013-07-30T11:47:53.383 に答える