![]() |
Delphi-Version: 10 Seattle
Exceptions in Threads nach außen weiterleiten
Hallo zusammen,
ich habe angefangen, mich mit Threads zu beschäftigen und erste Versuche unternommen, Threads in meine Anwendung einzubauen. Ein Problem, das ich dabei habe, sind Exceptions. In meiner Anwendung verwende ich EurekaLog und möchte, dass der Callstack bei Exceptions im Bugreport enthalten ist. Daher muss ich die Exception irgendwie nach außen an den aufrufenden bzw. an den Main-Thread weiterleiten. Ich habe versucht, die Exception im Thread abzufangen und mit einem Event (OnError) an den Main-Thread weiterzuleiten (TThreadEx ist eine von TThread abgeleitete Klasse von EurekaLog):
Delphi-Quellcode:
Mein Problem ist in der Prozedur DoOnError. Ich habe festgestellt, dass ein Memory-Leak entsteht, wenn ich E.Free nicht aufrufe (ReleaseExceptionObject hat nichts gebracht). Den Code finde ich aber ziemlich hässlich und ich könnte mir vorstellen, dass es eine schönere Lösung gibt. Meine Idee ist, in dem Event-Handler FOnError, welcher im Moment im wegen dem Synchronize im Main-Thread ausgeführt wird, die Exceptions zu behandeln, also z. B. einfach weiterzuleiten oder mit Exception.RaiseOuterException eine neue Exception herum zu packen oder die "verschwinden" zu lassen (nachdem man entsprechend darauf reagiert hat).
TThreadErrorEvent = procedure(const E: Exception) of object;
TBaseThread = class(TThreadEx) strict private FOnError: TThreadErrorEvent; procedure DoOnError(const E: Exception); strict protected procedure Execute; override; final; procedure Run; virtual; abstract; public constructor Create(CreateSuspended: Boolean); property OnError: TThreadErrorEvent read FOnError write FOnError; end; implementation constructor TBaseThread.Create(CreateSuspended: Boolean); begin inherited Create(CreateSuspended); end; procedure TBaseThread.DoOnError(const E: Exception); var existingException: Exception; begin if (@FOnError = nil) OR (E = nil) then Exit; existingException := AcquireExceptionObject; Synchronize(procedure begin try FOnError(existingException); except on NewException: Exception do begin if NewException <> existingException then E.Free; // exception has been wrapped or another exception has been raised raise; end; end; // exception has not been re-raised and not been wrapped E.Free; end); end; procedure TBaseThread.Execute; begin try inherited; Run; except on E: Exception do DoOnError(E); end; end; Eine Lösung wäre vllt., das AcquireExceptionObject nicht im DoOnError zu machen sondern jeweils im Event-Handler, das man für FOnError setzt. Das möchte ich jedoch vermeiden, weil ich es sonst jedes Mal neu implementieren muss. Und noch eine weitere Frage: Ich rufe das Event FOnError durch das Synchronize im Kontext des Main-Threads auf. Gibt es eine Möglichkeit, dies im Kontext eines anderen Threads auszuführen? Wenn ich beispielsweise aus dem Main-Thread einen Thread A starte, welcher wiederum einen Thread B startet, dann möchte ich, dass Thread B einen aufgetretenen Fehler an Thread A schickt und nicht an den Main-Thread. Ich bin für jede Hilfe dankbar :) |
AW: Exceptions in Threads nach außen weiterleiten
Wenn du die Exception im Execute nicht abfängst, dann kannst du sowas machen:
Delphi-Quellcode:
unit Unit6;
interface uses System.SysUtils, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TMyThread = class(TThread) protected procedure Execute; override; end; TForm6 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private FThread: TMyThread; procedure OnMyThreadTerminate(Sender: TObject); public { Public declarations } end; var Form6: TForm6; implementation {$R *.dfm} procedure TForm6.Button1Click(Sender: TObject); begin FThread := TMyThread.Create; FThread.OnTerminate := OnMyThreadTerminate; FThread.FreeOnTerminate := True; end; { TMyThread } procedure TMyThread.Execute; var a, b: Integer; begin inherited; b := 0; a := a div b; end; procedure TForm6.OnMyThreadTerminate(Sender: TObject); var ThreadException: Exception; begin // das hier wird im Kontext des Main-Threads automatisch aufgerufen. if Assigned(Sender) and (Sender is TThread) then begin ThreadException := Exception(TThread(Sender).FatalException); if Assigned(ThreadException) then ShowMessage(ThreadException.Classname + ' : ' + ThreadException.Message); end; end; end. |
AW: Exceptions in Threads nach außen weiterleiten
Fakt ist:
Delphi-Quellcode:
tut entgegen der Doku (unter Windows) nichts. Der Code ist vorhanden, aber aus unerklärlichen Gründen von einer leeren Methode verdeckt.
ReleaseExceptionObject()
Siehe: ![]() Man muss die explizit mit
Delphi-Quellcode:
geholte Exception auch explizit wieder freigeben.
AcquireExceptionObject()
|
AW: Exceptions in Threads nach außen weiterleiten
Zitat:
Zitat:
|
AW: Exceptions in Threads nach außen weiterleiten
Zitat:
Wenn, dann gäbe es dafür ![]() Mit ![]()
Delphi-Quellcode:
Das kannst du dann später auch in einen anderen thread mitnehmen und da machen was du willst.
try
... except MyException := AcquireExceptionObject as Exception; //MyExceptAddr := ExceptAddr; end; Hier am Ende natürlich nicht das
Delphi-Quellcode:
vergessen. (außer du löst die Exception erneut aus -> raise)
MyException.Free;
Mit
Delphi-Quellcode:
raise MyException;
oder
Delphi-Quellcode:
raise MyException at MyExceptAddr;
kkönntest du die Exception irgendwo erneut auslösen, auch in einem anderen Thread. PS: TThread hat ein Property ![]() Wenn du diese Exception aber aus dieser Methode mitnehmen/weiterreichen willst, dann mußt du sie kopieren. (Exception macht anschließend immer ein Free auf dieses Objekt)
Delphi-Quellcode:
und anschließend
MyException := Exception.Create(FatalException.Message);
Delphi-Quellcode:
,
raise MyException;
Delphi-Quellcode:
inkl. der ursprünglichen Exception-Klasse
MyException := ExceptClass(FatalException.ClassType).Create(FatalException.Message);
oder
Delphi-Quellcode:
und
MyExceptionMessage := FatalException.Message;
Delphi-Quellcode:
raise Exception.Create(MyExceptionMessage);
Delphi fängt im TThread-Execute, im Synchronize und in VCL-Events alle Exception ab. Allerdings werden ausschließlich Exceptions des Hauptthreads automatisch angezeigt. (Exception-Fenster) und die in Threads gehen ins Nirvana, wenn sie niemand behandelt. Würde eine Exception bis zum Windows durchrauschen, ohne abgefangen zu werden, dann würde sofort der gesamte Prozess abgeschossen. (Programm beendet) Darum macht Delphi das. |
AW: Exceptions in Threads nach außen weiterleiten
Zitat:
Hallo himitsu, danke für deine ausführliche Erklärung. Ich hätte dazu jedoch noch ein paar Fragen: Zitat:
Unsicher bin ich mir, wie Delphi sich verhält, wenn ich im Eventhandler Exception.RaiseOuterException verwende. Dann wird um die Exception, deren Kontrolle ich mir mit AcquireExceptionObject geholt habe, als InnerException einer neuen Exception angehängt, richtig? Nimmt sich Delphi in diesem Fall die Kontrolle zurück oder muss ich die Exception selbst wieder freigeben, so wie ich es mache, wenn NewException <> existingException? Zitat:
Delphi-Quellcode:
dann bekomme ich im except-Block die Exception, die ich innen ausgelöst habe. Das ist auch etwas, was ich an Threads noch nicht verstanden habe: Wenn ich den Code so ausführe, wird die Exception dann zweimal ausgelöst, also einmal im Main-Thread und einem im Unter-Thread? Wenn ich den Debugger benutze, dann sieht es fast danach aus.
try
Synchronize(procedure begin raise Exception.Create('Test'); end); except // Exception behandeln end; Zitat:
Und noch eine letzte Frage: Das OnTerminate-Event wird immer im Kontext des Threads ausgeführt, aus dem der Unter-Thread gestartet wurde, oder? Besteht die Möglichkeit, ein Event so wie das OnError-Event aus meinem ersten Beitrag im Kontext eines anderen Threads (nicht der Main-Thread!) auszuführen? Also so etwas wie Synchronize und Queue, aber nicht für den Main-Thread. Oder ist es eine schlechte Architektur, wenn man versucht, etwas mit einem anderen als dem Main-Thread zu synchronisieren? Da ich gerade ziemlich viele offene Fragen bzgl. Threads habe: Kann jemand ein Buch empfehlen, das sich vorwiegend mit Threads in Delphi beschäftigt? |
Alle Zeitangaben in WEZ +1. Es ist jetzt 12:42 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz