Повышение исключения в 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 перехватывает все исключения, но моя программа их не показывает.
РЕДАКТИРОВАТЬ: Это было решение Cosmin, которое работало в конце - и причина, по которой это не поначалу, было потому, что я не добавил переменную ErrMsg, вместо этого я просто поместил все, что переменная будет содержать в Synchronize, что не будет работать Однако я понятия не имею, почему. Я понял это, когда у меня не было других идей, и я просто возился с решениями.
Как всегда, шутка на меня. = Р
6 ответов
Вот мое очень, очень короткое "мнение" по этому вопросу. Он работает только на 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;
Что-то очень важное, что вам нужно понять о многопоточной разработке:
Каждый поток имеет свой стек вызовов, почти как если бы они были отдельными программами. Это включает в себя основной поток вашей программы.
Потоки могут взаимодействовать друг с другом только определенным образом:
- Они могут работать с общими данными или объектами. Это может привести к проблемам параллелизма "гоночные условия", и поэтому вы должны быть в состоянии помочь им "красиво делиться данными". Что подводит нас к следующему пункту.
- Они могут "сигнализировать друг другу", используя различные подпрограммы поддержки ОС. К ним относятся такие вещи, как:
- Мьютексы
- Критические разделы
- События
- И, наконец, вы можете отправлять сообщения в другие темы. При условии, что поток каким-то образом написан как получатель сообщения.
Примечание: обратите внимание, что потоки не могут строго говоря вызывать другие потоки напрямую. Если, например, поток A попытается вызвать поток B напрямую, это будет шагом в стеке вызовов потока A!
Это подводит нас к теме вопроса: "в моих темах не возникают исключения"
Причина этого заключается в том, что все исключение:
- Запишите ошибку
- И раскрутите стек вызовов. <- NB: Ваш экземпляр TThread не может разматывать стек вызовов основного потока и не может произвольно прерывать выполнение основных потоков.
Таким образом, TThread не будет автоматически сообщать об исключениях в ваше основное приложение.
Вы должны принять четкое решение о том, как вы хотите обрабатывать ошибки в потоках, и реализовать их соответствующим образом.
Решение
- Первый шаг такой же, как в однопоточном приложении. Вам нужно решить, что означает ошибка и как должен реагировать поток.
- Должен ли поток продолжить обработку?
- Должна ли нить прерваться?
- Должна ли ошибка регистрироваться / сообщаться?
- Ошибка требует решения пользователя? <- Это наиболее сложная реализация, поэтому мы пока пропустим ее.
- Как только это будет решено, реализуйте соответствующий обработчик исключений.
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
обработать исключение. Это абсолютно нет-нет! Делая это, вы притворяетесь, что, несмотря на исключение, все в порядке. Ваш поток пробует следующий шаг, но на самом деле гарантированно потерпит неудачу! Это не способ обработки исключений!
Потоки не распространяют исключения автоматически в другие потоки. Так что вы должны разобраться с этим сами.
Рафаэль изложил один подход, но есть альтернативы. Решение Рафаэль указывает синхронно на исключительную ситуацию, направляя ее в основной поток.
В одном из моих собственных применений потоков, пула потоков, потоки перехватывают и берут на себя ответственность за исключения. Это позволяет управляющему потоку обрабатывать их по своему усмотрению.
Код выглядит так.
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, и этот подход полезен.
Обратите внимание, что если вы не вызовете исключение, которое было захвачено, его необходимо уничтожить, иначе у вас будет утечка памяти.
Что ж,
Без вашего исходного кода будет тяжело, но я проверил это:
Как обрабатывать исключения в объектах 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;
Ранее я использовал 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;
Странно, что все ответили на этот вопрос, но не смогли обнаружить очевидную проблему: учитывая, что исключения, возникающие в фоновом потоке, являются асинхронными и могут происходить в любое время, это означает, что отображение исключений из фонового потока может вызвать случайное всплывающее диалоговое окно время для пользователя, вполне возможно, показывая исключение, которое не имеет ничего общего с тем, что пользователь делает в данный момент. Я сомневаюсь, что это может улучшить пользовательский опыт.