Создание исключения в 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;
странно, что все ответили на этот вопрос, но не смогли обнаружить очевидную проблему: учитывая, что исключения, возникающие в фоновом потоке, являются асинхронными и могут возникать в любое время, это означает, что отображение исключений из фонового потока будет всплывать диалоговое окно в случайное время для пользователя, вполне возможно, показывая исключение, которое не имеет ничего общего с тем, что пользователь делает в данный момент. Я сомневаюсь, что это может улучшить пользовательский интерфейс.