在TThread执行中引发异常?

Jef*_*eff 8 delphi multithreading exception raise tthread

我刚刚意识到我的异常没有在我的线程中向用户显示!

起初我在我的线程中使用它来引发异常,这不起作用:

except on E:Exception do
begin
  raise Exception.Create('Error: ' + E.Message);
end;
Run Code Online (Sandbox Code Playgroud)

IDE向我显示了异常,但我的应用程序没有!

我四处寻找解决方案,这就是我发现的:

Delphi线程异常机制

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

这些都不适合我.

这是我的Thread单元:

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.
Run Code Online (Sandbox Code Playgroud)

如果您需要更多信息,请告诉我.

再次:IDE捕获所有异常,但我的程序没有显示它们.

编辑:Cosmin的解决方案最终起作用 - 而且它最初没有的原因是因为我没有添加ErrMsg变量,而是我只是将变量包含的任何内容放入Synchronize中,这将不起作用,但我不明白为什么.当我没有其他想法时我才意识到这一点,我只是搞砸了解决方案.

一如既往,这个笑话就在我身上.= P

Dis*_*ned 13

您需要了解有关多重开发的一些非常重要的事情:

每个线程都有自己的调用堆栈,几乎就像它们是独立的程序一样.这包括程序的主线程.

线程只能以特定方式相互交互:

  • 他们可以对共享数据或对象进行操作.这可能导致并发问题的"竞争条件",因此您需要能够帮助他们"很好地共享数据".这将我们带到下一点.
  • 他们可以使用各种操作系统支持例程"相互发信号".其中包括:
    • 互斥
    • 关键部分
    • 活动
  • 最后,您可以将消息发送到其他线程.如果线程以某种方式被写为消息接收器.

注意:请注意,线程不能严格地直接调用其他线程.例如,如果线程A试图直接调用线程B,那么这将是线程A的调用堆栈的一个步骤!

这将我们带到问题的主题:"我的帖子中没有提出异常"

原因是所有例外情况都是:

  • 记录错误
  • 解除调用堆栈.< - NB:你的TThread实例无法解开主线程的调用堆栈,也不能随意中断主线程的执行.

因此,TThread不会自动向您的主应用程序报告异常.

您必须明确决定如何处理线程中的错误,并相应地实现.

  • 第一步与单线程应用程序中的步骤相同.您需要确定错误的含义以及线程应如何反应.
    • 线程应继续处理吗?
    • 线程应该中止吗?
    • 是否应记录/报告错误?
    • 错误是否需要用户决定?< - 这是迄今为止最难实现的,所以我们暂时不会这样做.
  • 确定后,实现适当的excpetion处理程序.
  • TIP: Make sure the exception doesn't escape the thread. The OS won't like you if it does.
  • 如果您需要主程序(线程)向用户报告错误,您有几个选项.
    • 如果编写线程以返回结果对象,则很容易:进行更改,以便在出现问题时可以返回该对象中的错误.
    • 向主线程发送消息以报告错误.注意,主线程已实现消息循环,因此应用程序将在处理该消息后立即报告错误.

编辑:指示要求的代码示例.

如果您只想通知用户,那么Cosmind Prund的答案 应该适用于Delphi 2010.旧版本的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;
Run Code Online (Sandbox Code Playgroud)

关于杰夫自己答案的一些重要更正,包括他的问题中显示的实施:

Terminate只有当你的线程在while not Terminated do...循环中实现时,调用才有意义.看看这个Terminate方法实际上做了什么.

打电话Exit是不必要的浪费,但你可能因为你的下一个错误而这样做了.

在您的问题中,您将自己包装每个步骤try...except来处理异常.这绝对不是不行!通过这样做,你假装即使发生了异常,一切都还可以.你的线程尝试下一步,但实际上保证失败!这不是处理异常的方法!


Cos*_*und 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;
Run Code Online (Sandbox Code Playgroud)


Dav*_*nan 6

线程不会自动将异常传播到其他线程.所以你必须自己处理它.

拉斐尔概述了一种方法,但也有其他选择.解决方案Rafael指出通过将其编组到主线程中来同步处理异常.

在我自己使用线程的一个线程池中,线程捕获并接管异常的所有权.这允许控制线程随意处理它们.

代码看起来像这样.

procedure TMyThread.Execute;
begin
  Try
    DoStuff;
  Except
    on Exception do begin
      FExceptAddr := ExceptAddr;
      FException := AcquireExceptionObject;
      //FBugReport := GetBugReportCallStackEtcFromMadExceptOrSimilar.
    end;
  End;
end;
Run Code Online (Sandbox Code Playgroud)

如果控制线程选择引发异常,它可以这样做:

raise Thread.FException at Thread.FExceptAddr;
Run Code Online (Sandbox Code Playgroud)

有时你可能有代码不能调用Synchronize,例如一些DLL,这种方法很有用.

请注意,如果您没有引发捕获的异常,则需要将其销毁,否则会发生内存泄漏.

  • 顺便说一句杰夫,"它不起作用"对我们来说永远不会有用. (2认同)