Laden...

Warum tauchen Exceptions an unerwarteter Stelle auf? [=> MessageBox.Show/DoEvents stören den Ablauf]

Letzter Beitrag vor 13 Jahren 8 Posts 4.447 Views
Warum tauchen Exceptions an unerwarteter Stelle auf? [=> MessageBox.Show/DoEvents stören den Ablauf]

Ich habe bei einem Projekt bemerkt das bei mir irgendwie Exceptions an der falschen Stelle auftreten. Ich frage mich wie kann das sein?

Zum Beispiel:
Ich baue eine Datenbankverbindung auf die eine Exception wirft. Diese Fange ich ab und gebe eine MessageBox aus. Diese MessageBox wirderrum löst exakt die selbe Exception nochmals aus nur mit einem andern StackTrace.

Anderes Beispiel:
Ich lese eine Socket asyncron aus. Beim Antworten geht etwas schief und ich bekomme eine Exception bei meiner Asyncronen Read Funktion anstatt dort wo der Fehler eigentlich auftritt.
Vom StackTrace her kann man auch überhauptnicht nachvollziehen wieso dort die Exception auftritt. Der StackTrace hat eine erstaunliche länge von über 250 Zeilen was auch nicht sonderlich hilfreich ist^^
Erst nach dem ich manuell an der entscheidenden stelle ein try catch hinzugefügt habe macht die Exception wieder Sinn.

Wie kommt so etwas?
Habe ich mein VS verstellt oder habe ich da irgendwie das .net Framework durcheinander gebracht?

Hallo dsdsfga,

Code würde die Sache wesentlich einfacher machen, oder StackTrace Ausschnitte... so kann man wieder nur Vermutungen anstellen - du willst doch selbst das dir geholfen wird. Dann mach es den Helfern so einfach wie nur irgendwie möglich.

Zum Beispiel:
Ich baue eine Datenbankverbindung auf die eine Exception wirft. Diese Fange ich ab und gebe eine MessageBox aus. Diese MessageBox wirderrum löst exakt die selbe Exception nochmals aus nur mit einem andern StackTrace.

Greifst du bei der Ausgabe eventuell wieder auf inkonsistente Felder der Datenbankverbindung zu?

Anderes Beispiel:
Ich lese eine Socket asyncron aus. Beim Antworten geht etwas schief und ich bekomme eine Exception bei meiner Asyncronen Read Funktion anstatt dort wo der Fehler eigentlich auftritt.
Vom StackTrace her kann man auch überhauptnicht nachvollziehen wieso dort die Exception auftritt.

Gleiche Vermutung wie oben...

Wenn du die Anwendung aufgrund von hoher Paralellität (z.B. mehrere asynchrone Anfragen) nicht vernünftig debuggen kannst, versuch das Problem mit gezieltem Logging zu suchen.

Gruß

Diese MessageBox wirderrum löst exakt die selbe Exception nochmals aus nur mit einem andern StackTrace.

Und du wunderst dich, warum der StackTrace ein anderer ist?
Kommt drauf an, wie du die Exceptions wieder auslöst. Ein throw exception; vernichtet den alten Stacktrace. Mit einen einfachen throw; bleibt er dir erhalten. Siehe auch Ausnahmen und Ausnahmebehandlung (C#-Programmierhandbuch).

Ich lese eine Socket asyncron aus. Beim Antworten geht etwas schief und ich bekomme eine Exception bei meiner Asyncronen Read Funktion anstatt dort wo der Fehler eigentlich auftritt.

Ja, das ist der Sinn bei einer asynchrones Ausführung.
Du kannst aber dein VisualStudio so einstellen, dass es bei jeder Exception erst einmal anhält (auch wenn die Exception irgendwo anders dann gefangen wird). Einfach ins Menü "Debuggen">"Ausnahmen" das Häkchen bei CLR-Exceptions/ausgelöst reinmachen.

Gruß,
dN!3L

Also das mit der MessageBox habe ich dort erläutert Entity Framework: Datenbank offline -> MessageBox wirft Exception.

Mit den asyncronen Zugriffen ich habe mit BeginRead gearbeitet in meinem Callback habe ich dann diesen Zugriff auf mein EF Objekt:

bool first=true;
foreach(User u in db.User) {
    if(first) {
        first = false;
    } else {
        socket.Write(";");
    }
    data = u.Username + ":" + u.HashedAndSaltedPassword + ":" + u.Role.Name;
    socket.Write(data);
}

Das .Write() ist eine Erweiterungsfunktion von mir

Je nach dem wie ich oft ich auf etwas zugegriffen habe kam es zu einer Exception weil der MySQL Treiber nicht auf zwei DataReader gleichzeitig verwalten kann, das passierte mir beim Zugriff auf einen Fremdschlüssel.
Die genaue Fehlermeldung war diese hier:> Fehlermeldung:

There is already an open DataReader associated with this Connection which must be closed first.

Die Lösung war recht simpel ich musste nur ein .ToList() ergänzen, damit habe ich dann eine unabhänige Kopie mit der ich arbeiten kann.

Diese EntityFrameworkException (oder ähnlich) wurde dann ganz wo anders vom Debugger angezeigt. An einer Stelle wo ich byteweise asyncron das Socket auslese. Mit einer IOException wurde mir das dann dort für mich völlig unverständlich angezeigt, wenn ich mich nicht irre war das sogar auch noch in einer andern TCP-Verbindung wo diese Exception angezeigt wurde. Weil erst nach meiner syncronen Antwort kam wieder ein BeginRead().

Hallo dsdsfga,

ist nun natürlich etwas blöd, dass es nun zwei Threads zu quasi demselben Thema gibt. Da der andere Thread schon ein paar Tage alt ist und dieser hier aktuell, antworte ich hier, obwohl sich die Antwort eher auf den anderen Thread ([Erledigt] Entity Framework: Datenbank offline -> MessageBox wirft Exception) bezieht.

Die Exception wird ja nicht von MessageBox.Show ausgelöst, sondern landet nur da.

Das liegt an folgendem:

WPF und Windows-Forms-Anwendungen benötigen eine Nachrichtenverarbeitungsschleife, um zu funktionieren. Ohne diese Schleife werden keine Benutzereingaben und überhaupt keine GUI-Event verarbeitet und das GUI blockiert. Die normale Nachrichtenverarbeitungsschleife ist in Application.Run (...) bzw. new Application().Run(...) implementiert und wird daher üblicherweise im Main ausgeführt.

Damit diese Schleife immer oft genug (pro Sekunde) durchlaufen wird, dürfen in GUI-Events keine Aktionen durchgeführt werden, die länger als 1/10s dauern (siehe [FAQ] Warum blockiert mein GUI?).

Da MessageBox.Show jedoch erst zurückkehrt, wenn die MessageBox geschlossen wurde und das typischerweise deutlich länger als 1/10s dauert, muss MessageBox.Show intern seine eigene Nachrichtenschleife ausführen, damit es nicht zur Blockierung des GUIs kommt. Während MessageBox.Show werden also weiter lustig alle GUI-Events verarbeitet.

Wenn in der Event-Behandlung eines solchen Events nun eine Exception auslöst und diese nicht rechtzeitig abgefangen wird, schlägt diese nun bei MessageBox.Show auf (und nicht - wie normalerweise - bei Application.Run). Also alles works as designed.

Und natürlich wird dieses Szenario vermieden, wenn die Exception sinnvoller- und korrekterweise rechtzeitig in der Event-Behandlung abgefangen wird.

Unabhängig vom Exception-Handling muss man sich immer bewusst sein, dass durch eine MessageBox - übrigens genauso wie durch das ebenfalls böse Form.ShowDialog und alle anderen Stellen, die direkt oder indirekt Application.DoEvents verwenden - die aktuelle Event-Behandlung unterbrochen und begonnen wird, anstehende Events zu verarbeiten, obwohl die aktuelle Ereignisbehandlung noch nicht abgeschlossen ist. Obwohl das GUI single-threaded arbeitet, kann und wird es durch MessageBoxen, andere modale Dialoge und DoEvents also dazu kommen, dass ein neuer (oder derselbe) EventHandler aufgerufen werden, obwohl der aktuelle EventHandler noch nicht zu Ende abgearbeitet ist. MessageBoxen und andere modale Dialoge sind also mit großer Vorsicht - oder noch besser überhaupt nicht - zu verwenden. Siehe auch Warum DoEvents Mist ist!.

Gute Gründe für das Verbannen von MessageBoxen und anderen modalen Dialogen finden sich in Warten auf Schließen einer anderen Form [und warum man Dialoge nicht modal machen sollte].

herbivore

Suchhilfe: 1000 Worte

Danke für die schöne Erklärung herbivore. Wenn das doch nur mal ein gutes Buch so präzise und verständlich erklären könnte... willst du nicht mal dein Wissen niederschreiben? 😃

Vorweg: Diesen Thread habe ich nur geöffnet, weil ich mich über Exceptions aufgeregt habe die dort auftauschen wo sie meiner Ansicht nach nicht hingehören.

Ah die Message-Loops die habe ich total vergessen. Ich habe testweise auch schon ausprobiert SetLastError auf 0 zu setzen falls da irgendwo noch ein alter Fehlercode steht.
Irgendwie muss man sich dann also doch noch mit WinAPI Programmierung beschäftigen um gewisse verhalten in .Net zu verstehen.

Der Code den ich dort genannt habe befindet sich im Konstruktor des MainWindows meines Verständnisses nach kein GUI-Thread. Wenn nicht bitte korrigieren!

Um das für mich nocheinmal kurz zu reflektieren:
Application.Run ist die MessageLoop für das ganze Programm bei WM_CREATE wird dann meine MainWindow Klasse generiert.
Den Ablauf innerhalb des MainWindows sehe ich so:
Der Konstruktor wird ausgeführt in dem dann durch aufruf von InitializeComponent() die MessageLoop erzeugt wird.
In der MessageLoop wiederrum wird bei WM_Create die WPF-GUI gestarten und GUI-Events und Exceptions verarbeiten.

[...]eine Exception auslöst und diese nicht rechtzeitig abgefangen wird [...]

Fange ich die Exception etwa nicht durch das try catch ab? Ich habe es ja auch ausprobiert diese Verarbeitung in einem zweiten (nicht verschachtelten) try catch durchzufüren wo meinem Verständnis nach das Exception handeling komplett abgeschlossen ist.

Nach dem mir bewust wurde das ich an der Reihenfolge der Initialisierung arbeiten muss klappt es nun endlich! Die MessageBox wird angezeigt und das Programm sauber beendet.

Nur was ist die Erklärung dafür das die IOException an der falschen stelle aufgetaucht ist? Hat quasi keiner sich für Exception interessiert und erst nach dem der ThreadPool den nächsten Aufruf verarbeitet hat ist dann dort die Exception auftetreten?

Insgesammt vielen Dank für deine Hilfe herbivore das hat mich jetzt wirklich sehr Geholfen!

Edit:
Irgendwie merkwürdig nach dem ich den Code jetzt etwas umformatiert habe geht meine MessageBox nach einer halben Sekunde von alleine zu. Was ist das denn schon wieder!?

Hallo dsdsfga,

Der Code den ich dort genannt habe befindet sich im Konstruktor des MainWindows meines Verständnisses nach kein GUI-Thread. Wenn nicht bitte korrigieren!

der Konstruktor des MainWindows sollte unbedingt im GUI-Thread ausgeführt werden.

Man kann es natürlich auch andersherum sehen: Der Thread, in dem das MainWindow erzeugt wird, ist der GUI-Thread. Diese Thread muss anschließend Application.Run ausführen, sonst wäre das MainWindow nicht funktionsfähig.

Der Konstruktor wird ausgeführt in dem dann durch aufruf von InitializeComponent() die MessageLoop erzeugt wird.

Nein, die MessageLoop befindet sich wie gesagt im Application.Run in der Main-Methode.

Fange ich die Exception etwa nicht durch das try catch ab?

Doch natürlich, nur reicht es in Windows-Anwendungen nicht, an einer zentralen Stelle try-catch zu verwenden, sondern wenn man alle Exceptions abfangen will, ohne dass die MessageLoop abgebrochen wird, muss man try-catch in jedem (GUI-)EventHandler (mindestens in denen auf oberste Ebene, also die, die direkt aus der MessageLoop aufgerufen werden) verwenden.

... und erst nach dem der ThreadPool den nächsten Aufruf verarbeitet hat ist dann dort die Exception auftetreten?

Exceptions werden immer innerhalb eines Threads geworfen, nie über Thread-Grenzen hinweg.

Irgendwie merkwürdig nach dem ich den Code jetzt etwas umformatiert habe geht meine MessageBox nach einer halben Sekunde von alleine zu. Was ist das denn schon wieder!?

Wenns nicht an einer Exception liegt, auf jeden Fall ein anderen Thema. 😃

Hallo myUnderTakeR,

Wenn das doch nur mal ein gutes Buch so präzise und verständlich erklären könnte... willst du nicht mal dein Wissen niederschreiben?

als ich mit Windows angefangen habe, musste man die Nachrichtenschleife noch explizit ausprogrammieren (while (...)), heute ist sie in Application.Run "versteckt". Das ist natürlich wesentlich komfortabler. Und in den meisten Fällen muss man sich (gedanklich) auch nicht darum kümmern, wie die (GUI-)EventHandler aufgerufen werden, es passiert einfach. Es gibt nur wenige Situation, wie hier, wo es hilfreich ist, hinter die Kulissen zu blicken.

Diesen Trend zur Abstraktion mag man bedauern, aber die Abstraktion versteckt eben auch viele unwichtige Details und entlastet damit das Hirn des Programmierers bzw. macht Kapazitäten für wichtigere Dinge frei. In machen speziellen Fällen, wäre es sicher auch hilfreich, wenn man auf Assembler-/Maschinencode-Ebene wüsste, was in bestimmten Situationen passiert. Das bedeutet aber noch lange nicht, dass der Aufwand, das zu lernen, in einem vernünftigen Verhältnis zu dem Nutzen steht, den man im Laufe des Lebens dadurch einfährt, wenn man später nicht sowieso schon wieder alles vergessen/verlernt hat.

Im Normalfall reicht es zu wissen:

MessageBoxen sind also mit großer Vorsicht - oder noch besser überhaupt nicht - zu verwenden.

Das fällt insofern besonders leicht, da MessageBoxen sowieso nicht mehr zeitgemäß sind. Diese Tatsache und die Alternativen habe ich z.B. in Warten auf Schließen einer anderen Form (und warum man Dialoge nicht modal machen sollte) beschrieben. Selbst fürs Debugging braucht man MessageBoxen nicht: Dazu gibt es Debugger oder - wenn mans doch gerne so ähnlich wie mit MessageBox.Show haben will - Logging (z.B. Debug.WriteLine).

herbivore