Создание исключения в Tthread Execute?

Я только что понял, что мои исключения не отображаются пользователю в моих потоках!

сначала я использовал это в своем потоке для создания исключения, которое не работает:

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 ловит все исключения, но моя программа их не показывает.

EDIT: это было решение Cosmin, которое сработало в конце - и причина, по которой это не во-первых, потому, что я не добавил переменную ErrMsg, вместо этого я просто поместил все, что переменная будет содержать в синхронизации, которая не будет работать, однако я понятия не имею, почему. Я понял это, когда у меня не было других идей, и я просто возился с решениями.

как всегда, шутка на мне. =P

6 ответов


вот мой очень, очень короткий "take" по этому вопросу. Он работает только на 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;

что-то очень важное, что вам нужно понять о мульти-theraded развития:

каждый поток имеет свой собственный call-стека, почти как будто это отдельные программы. Это включает в себя основной поток вашей программы.

потоки могут взаимодействовать друг с другом особым образом:

  • они могут работать с общими данными или объектами. это может привести к проблемам параллелизма 'гонки', и поэтому вы нужно иметь возможность помочь им "обмениваться данными красиво". Что подводит нас к следующему пункту.
  • они могут "сигнализировать друг другу", используя различные процедуры поддержки ОС. Они включают в себя такие вещи, как:
    • мьютексы
    • Критические Секции
    • событий
  • и, наконец, вы можете отправлять сообщения на другие темы. при условии, что поток каким-то образом был написан как сообщение приемник.

NB: обратите внимание, что потоки не могут строго говоря звоните прямо другие потоки. Если, например, поток A попытался вызвать поток B напрямую, это было бы шагом в стеке вызовов потока A!

это подводит нас к теме вопрос: "исключения не вызываются в моих потоках"

причина этого в том, что все исключение делает:

  • запись ошибка
  • и раскрутки стека вызовов.

так TThread не будет автоматически сообщить об исключениях из основного приложения.

вы должны принять четкое решение о том, как вы хотите обрабатывать ошибки в потоках, и соответственно реализовать.

решение

  • первый шаг такой же, как в однопоточном приложении. Вам нужно решить, что ошибка означает и как должен реагировать поток.
    • должен ли поток продолжать обработку?
    • должен ли поток прерваться?
    • должна ли ошибка регистрироваться / сообщаться?
    • требуется ли решение пользователя для ошибки?
  • как только это будет решено, реализуйте соответствующий обработчик excpetion.
  • TIP: Make sure the exception doesn't escape the thread. The OS won't like you if it does.
  • Если вам нужна основная программа (поток), чтобы сообщить об ошибке пользователю, у вас есть несколько вариантов.
    • если поток был написан для возврата объекта результата, то это легко: внесите изменения, чтобы он мог вернуть ошибку в этом объекте, если что-то пошло не так.
    • отправить сообщение в основной поток, чтобы сообщить об ошибке. Обратите внимание, что основной поток уже реализует цикл сообщений, поэтому приложение сообщит об ошибке, как только обработает это сообщение.

EDIT: образец кода для указанного требования.

если все вы хотите сделать, это уведомить пользователя, то 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;

некоторые важные исправления в собственном ответе Джеффа, включая реализацию, показанную в его вопросе:

вызов Terminate имеет значение только в том случае, если ваш поток реализован в while not Terminated do ... петля. Взгляните на то, что Terminate способ на самом деле.

вызов Exit это ненужная трата, но вы, вероятно, сделали это из-за следующей ошибки.

в вашем вопросе вы обертываете каждый шаг в свой собственный try...except для обработки исключения. Это абсолютное нет-нет! Делая это, вы делаете вид, что, хотя и произошло исключение, все в порядке. Ваш поток пытается сделать следующий шаг, но на самом деле гарантированно потерпит неудачу! Это не способ обработки исключений!


потоки не распространяют исключения автоматически в другие потоки. Так что ты должен разобраться с этим сам.

Рафаэль изложил один подход, но есть альтернативы. Решение, на которое указывает Рафаэль, касается исключения синхронно, выстраивая его в основной поток.

в одном из моих собственных применений потоковой обработки, пул потоков, потоки ловят и берут на себя владение исключениями. Это позволяет управляющему потоку обрабатывать их как это радует.

код выглядит так.

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, и этот подход полезен.

обратите внимание, что если вы не вызываете исключение, которое было захвачено, то оно должно быть уничтожено, иначе у вас есть утечка памяти.


Ну

это будет трудно без вашего исходного кода, но я проверил это:

как обрабатывать исключения в объектах TThread

и он отлично работает. Возможно, вам стоит взглянуть на него.

EDIT:

вы не следуете тому, что ссылки, которые вы указываете, говорят нам делать. Проверьте мою ссылку и вы увидите как это сделать.

EDIT 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;

EDIT 3:

Вы сказали, что не можете поймать EIdHTTPProtocolException. Но для меня это работает. Попробуйте этот образец и убедитесь в этом сами:

procedure TUpdaterThread.Execute;
begin
  Try
    raise EIdHTTPProtocolException.Create('test');
  Except
    HandleException;
  End;
end;

Я ранее использовал SendMessge для связи между потоками с помощью TWMCopyData, поэтому я думаю, что должно работать следующее:

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 чтобы конструктор в форме создал поток и обработал messsage в форме, которая его создала

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;

странно, что все ответили на этот вопрос, но не смогли обнаружить очевидную проблему: учитывая, что исключения, возникающие в фоновом потоке, являются асинхронными и могут возникать в любое время, это означает, что отображение исключений из фонового потока будет всплывать диалоговое окно в случайное время для пользователя, вполне возможно, показывая исключение, которое не имеет ничего общего с тем, что пользователь делает в данный момент. Я сомневаюсь, что это может улучшить пользовательский интерфейс.