8

自分の例外がスレッドでユーザーに表示されていないことに気づきました。

最初は、これをスレッドで使用して例外を発生させましたが、これは機能しません。

except on E:Exception do
begin
  raise Exception.Create('Error: ' + E.Message);
end;

IDEは例外を表示しますが、私のアプリは表示しません!

私は解決策を探しました、これは私が見つけたものです:

Delphiスレッド例外メカニズム

http://www.experts-exchange.com/Programming/Languages/Pascal/Delphi/Q_22039681.html

そして、これらのどちらも私にはうまくいきませんでした。

これが私のスレッドユニットです:

unit uCheckForUpdateThread;

interface

uses
  Windows, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
  IdHTTP, GlobalFuncs, Classes, HtmlExtractor, SysUtils, Forms;

type
  TUpdaterThread = class(TThread)
  private
    FileGrabber : THtmlExtractor;
    HTTP : TIdHttp;
    AppMajor,
    AppMinor,
    AppRelease : Integer;
    UpdateText : string;
    VersionStr : string;
    ExceptionText : string;
    FException: Exception;
    procedure DoHandleException;
    procedure SyncUpdateLbl;
    procedure SyncFinalize;
  public
    constructor Create;

  protected
    procedure HandleException; virtual;

    procedure Execute; override;
  end;

implementation

uses
  uMain;

{ TUpdaterThread }

constructor TUpdaterThread.Create;
begin
  inherited Create(False);
end;

procedure TUpdaterThread.Execute;
begin
  inherited;
  FreeOnTerminate := True;

  if Terminated then
    Exit;

  FileGrabber           := THtmlExtractor.Create;
  HTTP                  := TIdHTTP.Create(nil);
  try
    try
      FileGrabber.Grab('http://jeffijoe.com/xSky/Updates/CheckForUpdates.php');
    except on E: Exception do
    begin
      UpdateText := 'Error while updating xSky!';
      ExceptionText := 'Error: Cannot find remote file! Please restart xSky and try again! Also, make sure you are connected to the Internet, and that your Firewall is not blocking xSky!';
      HandleException;
    end;
    end;

    try
      AppMajor      := StrToInt(FileGrabber.ExtractValue('AppMajor[', ']'));
      AppMinor      := StrToInt(FileGrabber.ExtractValue('AppMinor[', ']'));
      AppRelease    := StrToInt(FileGrabber.ExtractValue('AppRelease[[', ']'));
    except on E:Exception do
    begin
      HandleException;
    end;
    end;

    if (APP_VER_MAJOR < AppMajor) or (APP_VER_MINOR < AppMinor) or (APP_VER_RELEASE < AppRelease) then
    begin
      VersionStr := Format('%d.%d.%d', [AppMajor, AppMinor, AppRelease]);
      UpdateText := 'Downloading Version ' + VersionStr;
      Synchronize(SyncUpdateLbl);
    end;

  finally
    FileGrabber.Free;
    HTTP.Free;
  end;
  Synchronize(SyncFinalize);
end;

procedure TUpdaterThread.SyncFinalize;
begin
  DoTransition(frmMain.TransSearcher3, frmMain.gbLogin, True, 500);
end;

procedure TUpdaterThread.SyncUpdateLbl;
begin
  frmMain.lblCheckingForUpdates.Caption := UpdateText;
end;

procedure TUpdaterThread.HandleException;
begin
  FException := Exception(ExceptObject);
  try
    Synchronize(DoHandleException);
  finally
    FException := nil;
  end;
end;

procedure TUpdaterThread.DoHandleException;
begin
  Application.ShowException(FException);
end;

end.

さらに情報が必要な場合はお知らせください。

繰り返しますが、IDEはすべての例外をキャッチしますが、私のプログラムはそれらを表示しません。

編集:最終的に機能したのはCosminのソリューションでしたが、最初は機能しなかったのは、ErrMsg変数を追加しなかったためです。代わりに、変数に含まれるものをすべてSynchronizeに配置しただけで、機能しませんでした。 、しかし、私には理由がわかりません。他にアイデアがなかったときに気づき、解決策をいじりました。

いつものように、冗談は私にあります。= P

4

6 に答える 6

13

マルチレーダー開発について理解する必要がある非常に重要なこと:

各スレッドには、まるで別々のプログラムであるかのように、独自の呼び出しスタックがあります。これには、プログラムのメインスレッドが含まれます。

スレッドは、特定の方法でのみ相互に対話できます。

  • 共有データまたはオブジェクトを操作できます。これは同時実行の問題につながる可能性があるため、「競合状態」が発生する可能性があるため、「データを適切に共有する」ことができるようにする必要があります。それは私たちを次のポイントに導きます。
  • これらは、さまざまなOSサポートルーチンを使用して「相互に信号を送る」ことができます。これらには、次のようなものが含まれます。
    • ミューテックス
    • クリティカルセクション
    • イベント
  • そして最後に、他のスレッドにメッセージを送信できます。スレッドが何らかの方法でメッセージ受信者になるように記述されている場合。

注意:スレッドは厳密に言えば、他のスレッドを直接呼び出すことはできないことに注意してください。たとえば、スレッドAがスレッドBを直接呼び出そうとした場合、それはスレッドAの呼び出しスタックのステップになります。

これにより、「私のスレッドでは例外が発生していません」という質問のトピックに移動します。

この理由は、すべての例外が次のとおりであるためです。

  • エラーを記録する
  • そして、コールスタックをほどきます。<-注:TThreadインスタンスは、メインスレッドのコールスタックを巻き戻すことができず、メインスレッドの実行を任意に中断することはできません。

そのため、TThreadはメインアプリケーションに例外を自動的に報告しません。

スレッドのエラーをどのように処理するかを明確に決定し、それに応じて実装する必要があります。

解決

  • 最初のステップは、シングルスレッドアプリケーション内の場合と同じです。エラーの意味とスレッドの反応 を決定する必要があります。
    • スレッドは処理を続行する必要がありますか?
    • スレッドを中止する必要がありますか?
    • エラーをログに記録/報告する必要がありますか?
    • エラーにはユーザーの決定が必要ですか?<-これは実装がはるかに難しいので、ここではスキップします。
  • これが決まったら、適切な例外ハンドラーを実装します。
  • TIP: Make sure the exception doesn't escape the thread. The OS won't like you if it does.
  • エラーをユーザーに報告するためにメインプログラム(スレッド)が必要な場合は、いくつかのオプションがあります。
    • スレッドが結果オブジェクトを返すように作成されている場合、それは簡単です。何か問題が発生した場合にそのオブジェクトのエラーを返すことができるように変更を加えます。
    • エラーを報告するためにメインスレッドにメッセージを送信します。メインスレッドはすでにメッセージループを実装しているため、アプリケーションはそのメッセージを処理するとすぐにエラーを報告することに注意してください。

編集:示された要件のコードサンプル。

ユーザーに通知するだけであれば、CosmindPrundの答えはDelphi2010 で完全に機能するはずです。古いバージョンのDelphiには、もう少し作業が必要です。以下は、概念的にはジェフ自身の答えに似ていますが、間違いはありません。

procedure TUpdaterThread.ShowException;
begin
  MessageDlg(FExceptionMessage, mtError, [mbOk], 0);
end;

procedure TUpdaterThread.Execute;
begin
  try

    raise Exception.Create('Test Exception');
    //The code for your thread goes here
    //
    //

  except
    //Based on your requirement, the except block should be the outer-most block of your code
    on E: Exception do
    begin
      FExceptionMessage := 'Exception: '+E.ClassName+'. '+E.Message;
      Synchronize(ShowException);
    end;
  end;
end;

ジェフ自身の回答に対するいくつかの重要な修正。これには、彼の質問に示されている実装が含まれます。

toの呼び出しは、スレッドが...ループTerminate内に実装されている場合にのみ関係します。メソッドが実際にwhile not Terminated do何をするかを見てみましょう。Terminate

の呼び出しExitは不要な無駄ですが、おそらく次の間違いのためにこれを行いました。

あなたの質問ではtry...except、例外を処理するために各ステップを独自にラップしています。これは絶対にノーノーです!これを行うことにより、例外が発生した場合でも、すべてが正常であると偽ります。あなたのスレッドは次のステップを試みますが、実際には失敗することが保証されています!これは例外を処理する方法ではありません!

于 2011-03-26T17:27:11.087 に答える
9

これが私の非常に短い「テイク」です。Delphi 2010+でのみ機能します(そのバージョンでは匿名メソッドが導入されているため)。すでに投稿されているより洗練された方法とは異なり、私のものはエラーメッセージのみを表示し、それ以上でもそれ以下でもありません。

procedure TErrThread.Execute;
var ErrMsg: string;
begin
  try
    raise Exception.Create('Demonstration purposes exception');
  except on E:Exception do
    begin
      ErrMsg := E.ClassName + ' with message ' + E.Message;
      // The following could be all written on a single line to be more copy-paste friendly  
      Synchronize(
        procedure
        begin
          ShowMessage(ErrMsg);
        end
      );
    end;
  end;
end;
于 2011-03-26T16:41:32.940 に答える
6

スレッドは、例外を他のスレッドに自動的に伝播しません。したがって、自分で対処する必要があります。

ラファエルは1つのアプローチを概説しましたが、代替案があります。Rafaelが指摘するソリューションは、例外をメインスレッドにマーシャリングすることにより、例外を同期的に処理します。

私自身のスレッドの使用法の1つであるスレッドプールでは、スレッドが例外の所有権をキャッチして引き継ぎます。これにより、制御スレッドはそれらを自由に処理できます。

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

procedure TMyThread.Execute;
begin
  Try
    DoStuff;
  Except
    on Exception do begin
      FExceptAddr := ExceptAddr;
      FException := AcquireExceptionObject;
      //FBugReport := GetBugReportCallStackEtcFromMadExceptOrSimilar.
    end;
  End;
end;

制御スレッドが例外を発生させることを選択した場合、次のように実行できます。

raise Thread.FException at Thread.FExceptAddr;

一部のDLLなど、Synchronizeを呼び出せないコードがある場合は、このアプローチが役立ちます。

キャプチャされた例外を発生させない場合は、破棄する必要があることに注意してください。そうしないと、メモリリークが発生します。

于 2011-03-26T15:00:21.927 に答える
3

上手、

あなたのソースコードなしでは難しいでしょうが、私はこれをテストしました:

TThreadオブジェクトで例外を処理する方法

そしてそれはうまくいきます。おそらくあなたはそれを見てみるべきです。

編集:

あなたはあなたが指摘したリンクが私たちにやるように言っていることをたどっていません。私のリンクをチェックしてください、そうすればあなたはそれをする方法を見るでしょう。

編集2:

それを試して、それがうまくいったかどうか教えてください:

 TUpdaterThread= class(TThread)
 private
   FException: Exception;
   procedure DoHandleException;
 protected
   procedure Execute; override;
   procedure HandleException; virtual;
 end;

procedure TUpdaterThread.Execute;
begin
  inherited;
  FreeOnTerminate := True;
  if Terminated then
    Exit;
  FileGrabber := THtmlExtractor.Create;
  HTTP := TIdHTTP.Create(Nil);
  try
    Try
      FileGrabber.Grab('http://jeffijoe.com/xSky/Updates/CheckForUpdates.php');
    Except
      HandleException;
    End;
    Try
      AppMajor := StrToInt(FileGrabber.ExtractValue('AppMajor[', ']'));
      AppMinor := StrToInt(FileGrabber.ExtractValue('AppMinor[', ']'));
      AppRelease := StrToInt(FileGrabber.ExtractValue('AppRelease[[', ']'));
    Except
      HandleException;
    End;
    if (APP_VER_MAJOR < AppMajor) or (APP_VER_MINOR < AppMinor) or (APP_VER_RELEASE < AppRelease) then begin
      VersionStr := Format('%d.%d.%d', [AppMajor, AppMinor, AppRelease]);
      UpdateText := 'Downloading Version ' + VersionStr;
      Synchronize(SyncUpdateLbl);
    end;
  finally
    FileGrabber.Free;
    HTTP.Free;
  end;
  Synchronize(SyncFinalize);

end;

procedure TUpdaterThread.HandleException;
begin
  FException := Exception(ExceptObject);
  try
    Synchronize(DoHandleException);
  finally
    FException := nil;
  end;
end;

procedure TMyThread.DoHandleException;
begin
  Application.ShowException(FException);
end;

編集3:

EIdHTTPProtocolExceptionをキャッチできないとおっしゃいました。しかし、それは私にとってはうまくいきます。このサンプルを試して、自分の目で確かめてください。

procedure TUpdaterThread.Execute;
begin
  Try
    raise EIdHTTPProtocolException.Create('test');
  Except
    HandleException;
  End;
end;
于 2011-03-26T14:49:44.747 に答える
2

以前、TWMCopyDataを使用したスレッド間通信にSendMessgeを使用したことがあるので、次のように機能すると思います。

Const MyAppThreadError = WM_APP + 1;

constructor TUpdaterThread.Create(ErrorRecieverHandle: THandle);
begin
    Inherited Create(False);
    FErrorRecieverHandle := Application.Handle;
end;

procedure TUpdaterThread.Execute;
var
    cds: TWMCopyData;
begin
  try
     DoStuff;
  except on E:Exception do
    begin
        cds.dwData := 0;
        cds.cbData := Length(E.message) * SizeOf(Char);
        cds.lpData := Pointer(@E.message[1]);         
        SendMessage(FErrorRecieverHandle, MyAppThreadError, LPARAM(@cds), 0);
    end;
  end;
end;

私は単純なデータ型または文字列を送信するためにのみ使用しましたが、必要に応じてより多くの情報を送信するように適合させることができると確信しています。

スレッドを作成したフォームでコンストラクターに追加Self.Handleし、スレッドを作成したフォームでメッセージを処理する必要があります

procedure HandleUpdateError(var Message:TMessage); message MyAppThreadError;
var
    StringValue: string;
    CopyData : TWMCopyData; 
begin
    CopyData := TWMCopyData(Msg);
    SetLength(StringValue, CopyData.CopyDataStruct.cbData div SizeOf(Char));
    Move(CopyData.CopyDataStruct.lpData^, StringValue[1], CopyData.CopyDataStruct.cbData);
    Message.Result := 0;
    ShowMessage(StringValue);
end;
于 2011-03-26T17:20:10.153 に答える
1

不思議なことに、誰もがこの質問に答えましたが、明らかな問題を見つけることができませんでした。バックグラウンドスレッドで発生した例外は非同期であり、いつでも発生する可能性があるため、バックグラウンドスレッドからの例外を表示すると、ダイアログボックスがランダムにポップアップ表示されます。ユーザーに何度も、ユーザーが現在行っていることとは何の関係もない例外を示している可能性があります。これを行うことで、ユーザーエクスペリエンスが向上する可能性があるとは思えません。

于 2011-03-26T23:04:15.557 に答える