Повышение исключения в TThread Execute? - PullRequest
8 голосов
/ 26 марта 2011

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

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

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

IDE показывает мне исключения, а мое приложение - нет!

Я искал решение, вот что я нашел:

Механизм исключения потока Delphi

http://www.experts -exchange.com / Программирование / Языки / 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, не будет работать, однако я понятия не имею, почему. Я понял это, когда у меня не было других идей, и я просто возился с решениями.

Как всегда, шутка на меня. = Р

Ответы [ 6 ]

13 голосов
/ 26 марта 2011

Что-то очень важное, что вам нужно понять о многопоточной разработке:

Каждый поток имеет свой собственный стек вызовов, почти , как если бы они были отдельнымипрограммы.Это включает основной поток вашей программы.

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

  • Они могут работать с общими данными или объектами. Это может привести к проблемам параллелизма, «гоночным условиям», и, следовательно, вы должны быть в состоянии помочь им «красиво делиться данными».Это подводит нас к следующему пункту.
  • Они могут «сигнализировать друг другу», используя различные подпрограммы поддержки ОС.К ним относятся такие вещи, как:
    • Мьютексы
    • Критические секции
    • События
  • И, наконец, вы можете отправлять сообщения в другие темы. При условии, что поток каким-то образом записан как получатель сообщения.

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

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

Причина этого заключается в том, что все исключение:

  • Запишите ошибку
  • И раскрутите вызов стека .<- NB: Ваш экземпляр TThread не может разматывать стек вызовов основного потока и не может произвольно прерывать выполнение основных потоков. </li>

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

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

Решение

  • Первый шаг такой же, как в однопоточном приложении.Вам необходимо решить, что означает ошибка и как должен реагировать поток.
    • Должен ли поток продолжить обработку?
    • Должен ли поток прерваться?
    • Должна ли ошибка регистрироваться / сообщаться?
    • Требуется ли решение пользователя?<- Это, безусловно, самый сложный для реализации, поэтому мы пока пропустим его. </li>
  • Как только это будет решено, реализуйте соответствующий обработчик исключений.
  • 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;

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

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

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

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

9 голосов
/ 26 марта 2011

Вот мое очень, очень короткое "мнение" по этому вопросу. Он работает только в 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;
6 голосов
/ 26 марта 2011

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

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

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

Код выглядит следующим образом.

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;

Иногда у вас может быть код, который не может вызвать Synchronize, например, некоторые библиотеки DLL, и этот подход полезен.

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

3 голосов
/ 26 марта 2011

Ну,

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

Как обрабатывать исключения в объектах 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;
2 голосов
/ 26 марта 2011

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

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;
1 голос
/ 27 марта 2011

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

...