Einzelnen Beitrag anzeigen

Scurra

Registriert seit: 19. Jan 2015
81 Beiträge
 
Delphi 10.3 Rio
 
#1

Exceptions in Threads nach außen weiterleiten

  Alt 30. Nov 2017, 07:58
Delphi-Version: 10 Seattle
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:
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;
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).
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

Geändert von Scurra (30. Nov 2017 um 08:12 Uhr)
  Mit Zitat antworten Zitat