Entscheidungstabelle: Nach einer unerwarteten Ausnahme beenden oder fortsetzen?
· Go Komura · Windows-Entwicklung, Ausnahmebehandlung, Design, C# / .NET, Zuverlässigkeit
Excel-Checkliste mit japanisch-englischen Tabellenblättern herunterladen
Diese Datei besteht aus den beiden Tabellenblättern Checklist-ja und Checklist-en und zerlegt die Entscheidungsachsen dieses Artikels in 4 Kategorien mit 27 Punkten: Fortsetzungsbedingungen / Beendigungsbedingungen / Umsetzungsansatz / Entscheidungsreihenfolge. Die Spalten Status und Notes bleiben leer, sodass sich die Datei direkt als Aufzeichnung für die Störungsbehebung oder als Checkliste für Design-Reviews verwenden lässt.
Bei unerwarteten Ausnahmen denkt man schnell in einer Zweiteilung: abstürzen lassen oder mit catch weitermachen. In der Praxis ist diese Zweiteilung allerdings etwas grob.
Was Sie eigentlich wissen wollen, ist, ob sich der möglicherweise beschädigte Bereich eingrenzen lässt.
- Lässt sich nur diese eine Operation als fehlgeschlagen beenden?
- Reicht es, nur diesen Bildschirm / diese Verbindung / diesen Worker neu zu initialisieren?
- Oder ist bereits die Integrität des gesamten Prozesses fraglich?
Betrachtet man es in dieser Reihenfolge, lässt es sich deutlich leichter ordnen.
Dieser Artikel stellt, ausgehend von C#-/.NET-Windows-Anwendungen, dauerhaft laufenden Anwendungen, Windows-Diensten und Werkzeugen zur Gerätekopplung, eine Entscheidungstabelle für die Bedingungen zusammen, unter denen nach einer unerwarteten Ausnahme fortgesetzt werden darf, und die Bedingungen, unter denen ein Beenden vorzuziehen ist.
1. Zunächst das Fazit
- Alles mit
catch (Exception)abzufangen und weiterzumachen ist in der Regel gefährlich. - Fortsetzen ist nur vertretbar, wenn drei Dinge zusammenkommen: die fehlgeschlagene Einheit lässt sich verwerfen, der gemeinsame Zustand lässt sich zurücksetzen, externe Nebenwirkungen lassen sich erklären.
- Ist die Verarbeitungsgrenze klar – eine UI-Operation, eine Eingabe, ein Job –, kann mitunter fortgesetzt werden.
- Sind dagegen gemeinsamer, veränderlicher Zustand, dauerhaft laufende Schleifen, der Hauptthread, Startvorgänge, native Grenzen oder ein Verdacht auf Speicherbeschädigung im Spiel, ist das Beenden vorzuziehen.
- Bei Ausnahmen wie
StackOverflowException,AccessViolationExceptionoderOutOfMemoryException, die die Gesundheit des gesamten Prozesses infrage stellen, ist es sicherer, nicht von einer Fortsetzung auszugehen. - WPF und Windows Forms bieten zwar Wege, unbehandelte Ausnahmen abzufangen und scheinbar weiterzumachen, aber fortsetzen können und sicher fortsetzen sind zwei verschiedene Dinge.
- Bei lange laufenden Diensten und Überwachungsanwendungen ist es oft sicherer und leichter zu diagnostizieren, abzustürzen und neu gestartet zu werden, als halb kaputt weiterzulaufen.
Kurz gesagt: Die entscheidende Achse ist, ob sich die Invarianten wiederherstellen lassen.
1.1 In diesem Artikel verwendete Begriffe
Bevor Sie die Entscheidungstabelle lesen, klären wir vorab nur die 5 Begriffe, die immer wieder vorkommen.
| Begriff | Bedeutung in diesem Artikel |
|---|---|
| Invariante | Ein Zustandsversprechen, das vor und nach der Verarbeitung immer gelten muss. Beispiele: „Die Summe der Posten stimmt mit dem Aggregatwert überein“, „Cache und Datenbankinhalt stimmen überein“, „Eine geöffnete Verbindung steht immer in der Verwaltungsliste“. Bricht das, ist alles Nachfolgende verdächtig |
| Fehlereinheit | Der Bereich, der bei einem Fehlschlag komplett verworfen werden kann. Eine Operation, ein Bildschirm, ein Job, eine Verbindung usw. |
| Externe Nebenwirkung | Eine Änderung, die den Prozess bereits verlassen hat. DB-Aktualisierungen, Dateischreibvorgänge, E-Mail-Versand, Befehle an ein Gerät – alles, was sich durch catch nicht zurücknehmen lässt |
| Subsystem | Eine Einheit, die sich gemeinsam stoppen und neu initialisieren lässt. Verbindung, Bildschirm, Worker, Kindprozess usw. |
FailFast |
Steht für Environment.FailFast. Eine API, die den Prozess sofort beendet, ohne try / finally oder Finalizer auszuführen; Details dazu in Abschnitt 9.7 |
2. Was dieser Artikel unter einer „unerwarteten Ausnahme“ versteht
2.1 Erwartet und unerwartet trennen
Zunächst: Eine seltene Ausnahme und eine unerwartete Ausnahme sind nicht dasselbe.
Zum Beispiel lässt sich Folgendes trotz geringer Häufigkeit als erwartet behandeln:
- Der Benutzer hat eine nicht existierende Datei gewählt
- Die Gegenstelle hat vorübergehend ein Timeout ausgelöst
- Eine Zeile der importierten CSV-Datei war fehlerhaft
- Ein Abbruch hat
OperationCanceledExceptionausgelöst - Ein Verstoß gegen eine Geschäftsregel soll genau diesen einen Vorgang fehlschlagen lassen
Das sind Fehler, bei denen der Umgang mit dem Fehlschlag schon im Design festgelegt werden kann.
Die unerwarteten Ausnahmen, um die es in diesem Artikel hauptsächlich geht, sehen dagegen so aus:
- Eine Annahme im eigenen Code ist zusammengebrochen, und es kam zu
NullReferenceExceptionoderInvalidOperationException - Mitten in der Aktualisierung eines gemeinsamen Zustands ist eine Ausnahme aufgetreten, und es ist unklar, wie weit die Änderung übernommen wurde
- Die übergeordnete Schleife einer Überwachungs- oder Nachrichtenverarbeitung ist abgestürzt
- An der Grenze zu COM / P/Invoke / einem Drittanbieter-SDK ist eine Störung aufgetreten
- Wie bei
AccessViolationExceptionoderStackOverflowExceptionschlägt bereits der grundlegende Gesundheitscheck des Prozesses fehl
Kurz gesagt: Es sind Fälle, in denen „nach dieser Ausnahme unklar ist, ob dem Zustand der Anwendung noch vertraut werden kann“.
2.2 Es sieht wie eine Zweiteilung aus, ist aber eigentlich eine Dreiteilung
Was dieses Thema unnötig verkompliziert, ist, „Fortsetzen“ als eine einzige Option zu behandeln.
In der Praxis lässt es sich meist in drei Stufen unterteilen.
| Wahl | Bedeutung |
|---|---|
| Nur diese Operation fehlschlagen lassen und fortsetzen | Der Bildschirm bleibt erhalten, aber nur dieser Speicher- oder Importvorgang gilt als fehlgeschlagen |
| Nur das Subsystem stoppen und fortsetzen | Nur Verbindung, Bildschirm, Worker oder Kindprozess neu initialisieren |
| Den Prozess beenden | Der Umfang der Zustandsbeschädigung lässt sich nicht mehr abschätzen, also von einem Neustart ausgehen |
„Die Anwendung setzt fort“ kann zwei sehr unterschiedliche Dinge bedeuten: so weitermachen, als wäre nichts gewesen, und den defekten Teil abtrennen und dann fortsetzen.
3. Die Entscheidungstabelle, die Sie zuerst ansehen sollten
3.1 Der Gesamtüberblick
Ausgehend von dieser Tabelle lässt sich die grobe Richtung meist schon festlegen.
| Situation | Erste Wahl | Grund |
|---|---|---|
| Nur eine Eingabe, eine Bildschirmoperation oder ein Job ist fehlgeschlagen, und der Zustand lässt sich verwerfen | Eher fortsetzen | Die Fehlereinheit lässt sich eingrenzen |
| Nach der Ausnahme lassen sich das betroffene Objekt oder die Verbindung verwerfen und neu aufbauen | Eher Subsystem neu initialisieren | Der beschädigte Bereich lässt sich lokalisieren |
| Der gemeinsame Zustand wurde teilweise aktualisiert, aber es ist unklar, wie weit die Änderung übernommen wurde | Eher beenden | Die Invarianten könnten gebrochen sein |
| Externe Nebenwirkungen wie DB / Datei / Gerätebefehl sind unvollständig, und Duplikate oder fehlende Übernahmen lassen sich nicht erklären | Eher beenden | Die Konsistenz mit der Außenwelt lässt sich nicht abschätzen |
| Die Überwachungsschleife, die Wiederverbindungsschleife oder die übergeordnete Schleife der Nachrichtenverarbeitung ist wegen einer unerwarteten Ausnahme abgestürzt | Eher beenden | Stillschweigend nur eine Funktion sterben zu lassen führt leicht zur Zombifizierung |
| Startvorgang, Konfigurationsladen, DI-Zusammenstellung oder Initialisierung einer notwendigen Abhängigkeit ist fehlgeschlagen | Eher als Startfehler beenden | Ein halb gestarteter Zustand ist gefährlicher |
AccessViolationException, StackOverflowException, eine schwerwiegende OutOfMemoryException oder ein Verdacht auf Beschädigung auf nativer Seite |
Eher sofort beenden | Die Gesundheit des gesamten Prozesses ist fraglich |
| Die gefährliche Verarbeitung ist in einem separaten Prozess isoliert, der übergeordnete Prozess bleibt unversehrt | Übergeordneter Prozess läuft weiter, Kindprozess neu starten | Der Störungsbereich ist bereits getrennt |
flowchart TD
A["Unerwartete Ausnahme"] --> B{"Verdacht auf Speicherbeschädigung / Stapelüberlauf / fatale Ressourcenerschöpfung?"}
B -- "Ja" --> Z["Beenden / FailFast / Neustart"]
B -- "Nein" --> C{"Lässt sich die fehlgeschlagene Einheit verwerfen?"}
C -- "Nein" --> Y["Eher beenden"]
C -- "Ja" --> D{"Lässt sich der gemeinsame Zustand zurücksetzen / neu initialisieren?"}
D -- "Nein" --> X["Subsystem stoppen oder beenden"]
D -- "Ja" --> E{"Lassen sich externe Nebenwirkungen erklären?"}
E -- "Nein" --> X
E -- "Ja" --> W["Fortsetzen, nur diese Operation fehlgeschlagen"]
Der „Verdacht auf Speicherbeschädigung“ in der ersten Verzweigung des Diagramms wird in Abschnitt 3.4 behandelt, FailFast oben rechts konkret in Abschnitt 9.7.
3.2 Was vor dem Ausnahmetyp zu prüfen ist
Es ist besser, sich nicht allein anhand des Ausnahmetyps sofort festzulegen. Zuerst sollten Sie Folgendes prüfen.
| Zu prüfen | Was Sie klären |
|---|---|
| Wo es passiert ist | UI-Ereignis, ein einzelner Job, übergeordnete Schleife, Startvorgang oder native Grenze |
| Wie weit es fortgeschritten ist | Ob sich Speicherzustand, DB, Dateien oder Gerätezustand unterwegs schon geändert haben |
| Möglicher Schadensbereich | Nur dieses Objekt, der ganze Bildschirm oder der ganze Prozess |
| Ist ein Rollback möglich? | Lässt es sich verwerfen und neu aufbauen, oder per Transaktion zurücksetzen |
| Externe Nebenwirkungen | Gesendet oder nicht gesendet, ob doppelte Ausführung sicher ist, ob eine Kompensation möglich ist |
| Überwachung / Neustart | Gibt es nach dem Beenden einen automatischen Neustart oder einen Wiederherstellungspfad |
3.3 Besonders riskante Ausnahmen
Sie müssen nicht jeden Ausnahmetyp im Detail durchgehen, aber es gibt einige, die man besser nicht mit einer Fortsetzung im Hinterkopf betrachtet.
| Ausnahme / Anzeichen | Erste Wahl | Grund |
|---|---|---|
StackOverflowException |
Eher sofort beenden | Der Aufrufstapel ist zusammengebrochen, eine normale Wiederherstellung ist kaum anzunehmen |
AccessViolationException |
Eher sofort beenden | Unzulässiger Zugriff auf geschützten Speicher; native Grenzen oder Speicherbeschädigung sind zu vermuten |
OutOfMemoryException |
Eher beenden | Wiederherstellungscode, der selbst weitere Zuweisungen benötigt, neigt zur Instabilität |
Unerwartete NullReferenceException / InvalidOperationException |
Kontextabhängig, aber eher beenden | Eine eigene Annahme ist zusammengebrochen, teilweise Änderungen könnten übrig geblieben sein |
| Eine unerwartete Ausnahme, die aus der übergeordneten Schleife entwichen ist | Eher beenden | Der Kern der Funktion ist tot, während der Prozess selbst gefährlich weiterlebt |
| Störung mit Ursprung in COM- / P/Invoke- / Drittanbieter-SDK-Callbacks | Sofort bis stark beenden | Die Sicherheit lässt sich allein von der Managed-Seite aus schwer beurteilen |
3.4 Woran man „Verdacht auf Speicherbeschädigung“ und „Zombifizierung“ tatsächlich erkennt
Diese beiden Begriffe klingen zwar nach Bauchgefühl, aber worauf man dabei tatsächlich schaut, ist klar festgelegt.
Zunächst die Symptome, die eine Speicherbeschädigung vermuten lassen.
| Was zu sehen ist | Wo Sie es sehen |
|---|---|
Der Prozess stürzt mit dem Ausnahmecode 0xc0000005 (Zugriffsverletzung) oder 0xc0000374 (Heap-Beschädigung) ab |
Ereignisanzeige > Windows-Protokolle > Anwendung, Eintrag „Anwendungsfehler“ (Ereignis-ID 1000) |
| Die Absturzstelle wechselt jedes Mal. Der Absturz erfolgt in Code, der zuletzt gar nicht angefasst wurde | Log, Aufrufstapel des Dumps |
| Der Absturz tritt nur auf, wenn ein bereits freigegebenes Objekt oder Handle angefasst wird | Reproduktionsschritte, Dump |
Der Absturz erfolgt bei einer nativen Freigaberoutine (free / delete / COM-Freigabe) |
Aufrufstapel des Dumps (z. B. ein Absturz innerhalb von ntdll.dll) |
| Werte, die nicht angefasst wurden, sind verändert. Dasselbe Eingabemuster liefert unterschiedliche Ergebnisse | Ein-/Ausgabelogs, Vergleich wiederholter Läufe |
0xc0000005 steht für STATUS_ACCESS_VIOLATION, 0xc0000374 für STATUS_HEAP_CORRUPTION – beides Werte, die in Microsofts NTSTATUS-Liste definiert sind. Besonders bei einer Heap-Beschädigung stürzt der Prozess nicht in dem Moment ab, in dem der Schaden entsteht, sondern dort, wo als Nächstes auf den beschädigten Heap zugegriffen wird – die Absturzstelle ist also nicht zwangsläufig die Ursache. Zeigt sich dieses Muster, ist es sicherer, davon auszugehen, dass ein catch auf Managed-Seite mit anschließendem Weiterlaufen keinen Sinn ergibt.
Als Nächstes die Symptome, die eine Zombifizierung vermuten lassen.
| Was zu sehen ist | Wo Sie es sehen |
|---|---|
| Der Prozess lebt, aber der Zeitpunkt der letzten Verarbeitung wird nicht aktualisiert | Log des letzten Verarbeitungszeitpunkts, Heartbeat |
| Nur die Anzahl der Warteschlangen- oder Eingangsordner-Einträge wächst immer weiter | Warteschlangenlänge, Anzahl unverarbeiteter Dateien |
| Ab einem bestimmten Zeitpunkt bricht das Log plötzlich ab | Anwendungslog |
| Der Bildschirm lässt sich bedienen, aber die Aktualisierung im Hintergrund steht still | Abgleich von Bildschirmanzeige und tatsächlichen Daten |
| Die Anzahl der Worker-Threads ist geringer als erwartet | Diagnoselog, Thread-Liste im Process Explorer |
Zombifizierung bedeutet nicht, dass der Prozess abgestürzt ist, sondern dass er lebt, ohne zu arbeiten. Bereiten Sie im Voraus Kennzahlen vor, die „laufende Arbeit“ anzeigen – letzter Verarbeitungszeitpunkt, Warteschlangenlänge, Heartbeat –, dann fällt sowohl die Entscheidung zwischen Fortsetzen und Beenden als auch die spätere Untersuchung deutlich leichter.
4. Nach dem Ort der Ausnahme entscheiden
4.1 UI-Ereignisse
UI-Ereignisse wie Button-Klicks, Bildschirmwechsel, Suche oder Dateiauswahl bieten vergleichsweise viel Spielraum zum Fortsetzen. Es gibt jedoch Bedingungen.
Leichter fortzusetzen ist es in Fällen wie diesen:
- Der Fehler ist vor dem Laden aufgetreten, der fachliche Zustand wurde noch nicht angefasst
- Nur ein temporärer Zustand innerhalb eines Dialogs ist defekt, der beim Schließen des Dialogs verworfen wird
- ViewModel oder Verbindung lassen sich nach der Ausnahme neu aufbauen
- Dem Benutzer kann ehrlich mitgeteilt werden, dass „diese Operation fehlgeschlagen ist“
Umgekehrt neigt es zum Beenden, sobald es so aussieht:
- Sowohl Bildschirm als auch Domänenzustand wurden teilweise aktualisiert
- Ein gemeinsamer Zustand wie static / Singleton / Cache, den auch andere Bildschirme sehen, wurde angefasst
- Nach der Ausnahme bleiben nur Button-Aktivierung oder Auswahlzustand übrig, deren Konsistenz unklar ist
- Eine unerwartete Ausnahme ist auf dem UI-Thread aufgetreten, und es ist unklar, wie weit Rendering oder Benachrichtigungen fortgeschritten sind
4.2 Einzeln verarbeitete Jobs / Anfragen
Das ist eine Grenze, an der sich leicht fortsetzen lässt.
- Eine Nachricht
- Eine Datei
- Eine HTTP-Anfrage
- Ein Importjob
- Ein Batch-Element
Sind solche Einheiten klar abgegrenzt, können Sie nur dieses eine Element fehlschlagen lassen und mit dem nächsten fortfahren.
Es gibt jedoch Voraussetzungen:
- Die Fehlereinheit ist von außen klar erkennbar
- Teilweise Änderungen werden durch Transaktionen oder Kompensation bereinigt
- Ein erneuter Durchlauf derselben Verarbeitung führt zu keinem beschädigten Ergebnis
- Fehler lassen sich in eine Quarantäne-Warteschlange oder ein Fehlerprotokoll auslagern
4.3 Dauerhaft laufende Schleifen / Überwachung / Warteschlangenverarbeitung
Das ist die Stelle, an der ein unbedachtes Fortsetzen am gefährlichsten ist.
Zum Beispiel:
- Wiederverbindungsschleifen
- Überwachungsschleifen
- Schleifen zum Konsumieren von Warteschlangen
- Regelmäßiges Polling
- Überwachung des Gerätezustands
- Dauerhafte Verarbeitung in einer Tray-Anwendung
Bei dieser Art von Verarbeitung ist das Beängstigende, dass die übergeordnete Schleife durch eine einzige unerwartete Ausnahme stirbt, während der Prozess allein überlebt.
Hier lohnt es sich, die Vorgehensweise zu trennen:
- Erwartete Ausnahmen an der Grenze der einzelnen Elementverarbeitung abfangen
- Entweicht eine unerwartete Ausnahme aus der übergeordneten Schleife, eher den Prozess beenden
4.4 Startvorgang
Behandelt man einen Fehler beim Start nach dem Motto „erst mal starten und dann weitersehen“, läuft die Anwendung mit fehlenden Funktionen weiter, und es wird später schwer, die Ursache einzugrenzen.
- Erforderliche Konfiguration lässt sich nicht lesen
- Versionsmigration ist fehlgeschlagen
- Ein erforderlicher Ordner oder ein Zertifikat fehlt
- Die Initialisierung eines Kerndienstes ist fehlgeschlagen
- Die Abhängigkeitskonfiguration ist defekt
In solchen Fällen ist als Startfehler beenden die klarere Wahl.
4.5 Native Grenzen / COM / P/Invoke / unsafe
Hier lohnt sich ein separater, etwas strengerer Blick.
- COM
- P/Invoke
- Was hinter C++/CLI liegt
- Drittanbieter-SDKs
- Nativer Code, der über einen Callback zurückkommt
- Verarbeitung mit
unsafe
Besonders bei folgenden Anzeichen ist das Beenden vorzuziehen:
AccessViolationException- Anzeichen, die auf Heap-Beschädigung oder einen doppelten Free hindeuten
- Handle-Anomalien, Verdacht auf Zugriff nach Freigabe
- Plötzlicher Absturz an der Callback-Grenze
5. Bedingungen, unter denen Fortsetzen vertretbar ist
Zusammengefasst sehen die Bedingungen, unter denen Fortsetzen vertretbar ist, so aus. Voraussetzung ist, dass diese im Wesentlichen zusammen erfüllt sind.
| Bedingung | Bedeutung |
|---|---|
| Die Fehlereinheit ist klar | Eine Operation, ein Bildschirm, ein Job, eine Verbindung – Sie wissen, was verworfen wird |
| Der Zustand lässt sich verwerfen | Er lässt sich verwerfen und neu aufbauen, oder als nicht übernommen behandeln |
| Der gemeinsame Zustand ist geschützt | Die Verunreinigung breitet sich nicht auf andere Funktionen aus |
| Externe Nebenwirkungen lassen sich erklären | Sie wissen, ob gesendet / nicht gesendet / ein erneutes Senden sicher ist |
| Sie können dem Benutzer ehrlich Bescheid geben | Sie können anzeigen, dass „dieser Vorgang fehlgeschlagen ist“ |
| Es lässt sich überwachen | Logs, Metriken und Dumps ermöglichen eine nachträgliche Untersuchung |
6. Bedingungen, unter denen Beenden vorzuziehen ist
Umgekehrt ist das Beenden vorzuziehen, wenn Folgendes zutrifft.
- Unklar ist, was unterwegs alles geändert wurde
- Gemeinsamer, veränderlicher Zustand wurde angefasst, und die Konsistenz lässt sich nicht abschätzen
- Die Lebenszyklusverwaltung von Sperren, Warteschlangen, Threads oder Überwachungsschleifen ist defekt
- Doppelte / fehlende / unvollständige externe Nebenwirkungen lassen sich nicht erklären
- Der Startvorgang oder die Initialisierung der Kerninfrastruktur ist fehlgeschlagen
- Native Grenzen oder eine Speicherbeschädigung stehen im Verdacht
Auf dieser Stufe wirkt eine Vorkehrung, die das Wiederherstellen nach einem Absturz erleichtert, besser als ein Kunstgriff für ein sauberes Fortsetzen.
7. Empfehlungen nach typischen Mustern
Die Entscheidungstabelle aus 3.1 ist in Form von Bedingungen formuliert, sodass es beim Anwenden auf eine konkrete Situation zu Unsicherheit kommen kann. Diese Tabelle ordnet diese Bedingungen häufig anzutreffenden Situationen zu. Im Zweifel ist es schneller, zunächst mit 3.1 die Bedingungen zu prüfen und danach in dieser Tabelle die passende Zeile zu suchen.
| Muster | Empfehlung | Grund |
|---|---|---|
| Über den „Datei öffnen“-Button wurde ein nicht existierender Pfad angegeben | Nur diese Operation fehlschlagen lassen, fortsetzen | Die Zustandsbeschädigung ist lokal begrenzt |
| Nur eine Zeile des CSV-Imports war defekt | Eine Zeile oder eine Datei fehlschlagen lassen, fortsetzen | Die Fehlereinheit lässt sich leicht eingrenzen |
Beim Speichern eines Bildschirms trat mitten im Vorgang eine unerwartete NullReferenceException auf |
Bildschirm neu aufbauen bis eher beenden | Unklar, wie weit ViewModel / fachlicher Zustand geändert wurden |
| Eine Warteschlangennachricht verletzte eine Geschäftsregel | Nur diese Nachricht fehlschlagen lassen, fortsetzen | Sie lässt sich in eine Quarantäne-Warteschlange auslagern |
| Die übergeordnete Schleife der Warteschlangenverarbeitung ist wegen einer unerwarteten Ausnahme abgestürzt | Eher den Prozess beenden | Der Lebenszyklus des gesamten Workers ist defekt |
| Beim Start lässt sich eine erforderliche Konfiguration nicht lesen | Als Startfehler beenden | Ein halb gestarteter Zustand ist gefährlicher |
AccessViolationException rund um einen Drittanbieter-SDK-Callback |
Eher sofort beenden | Die Möglichkeit einer Speicherbeschädigung lässt sich nicht ausschließen |
| Nur eine nicht wesentliche Telemetrieübertragung ist fehlgeschlagen | Nur diese Funktion deaktivieren, fortsetzen | Störungsbereich lässt sich von der Hauptfunktion trennen |
8. Häufige Fallstricke
8.1 catch (Exception), das nur ein Log ausgibt und weitermacht
Das ist ziemlich gefährlich. Es verdeckt die Ursache und verlängert leicht das Leben eines beschädigten Zustands.
8.2 Der Versuch, im letzten unbehandelten Ausnahme-Handler wiederherzustellen
AppDomain.UnhandledException, Application.ThreadException, DispatcherUnhandledException und Ähnliches sind als letzte Aufzeichnungsstelle nützlich, aber kein magischer Wiederherstellungspunkt.
8.3 Bei vorhandenen externen Nebenwirkungen unbedacht erneut versuchen
Wird bei Gerätebefehlen, E-Mail-Versand, Abrechnungen, Dateiverschiebungen oder DB-Aktualisierungen ohne Sicherheit für eine erneute Ausführung ein Retry durchgeführt, rückt diesmal ein Vorfall mit doppelter Ausführung in den Vordergrund.
8.4 Die UI am Leben lassen, obwohl die Überwachungsschleife tot ist
Eine Anwendung, die nur äußerlich lebt, aber keine Arbeit mehr verrichtet, wirkt auf den Benutzer normal, wodurch die Entdeckung sich verzögert. Es lohnt sich, die in 3.4 genannten Symptome der Zombifizierung von der Überwachungsseite aus erfassbar zu machen.
8.5 „Wir wollen keinen Absturz“ sagen, ohne dafür zu entwerfen
Wenn Sie keinen Absturz wollen, gibt es Dinge, die vorher eingerichtet werden müssen.
- Automatischer Neustart
- Sitzungswiederherstellung
- Speichern von Zwischenergebnissen
- Sicherheit für eine erneute Ausführung
- Trennung des Störungsbereichs
9. Ordnungspunkte bei der Umsetzung
9.1 catch-Stellen an die Grenzen verlagern
Statt in tiefen Schichten alles Mögliche abzufangen, lässt es sich leichter ordnen, wenn Sie dort abfangen, wo sich die Fehlereinheit definieren lässt, etwa:
- UI-Operationsgrenze
- Grenze einer Anfrage
- Grenze eines Jobs
- Grenze einer Verbindung
- Prozessgrenze
Bei einem Import, der Element für Element verarbeitet wird, platzieren Sie das catch zum Beispiel innerhalb der Schleife. Das ist hier die Fehlereinheit.
// C# / .NET 8. Beispiel für eine Schleife, die Element für Element verarbeitet
// und die Fehlereinheit im Inneren der Schleife einschließt.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
public sealed record ImportItem(string Id, string Payload);
public sealed record ImportFailure(string Id, string ExceptionType, string Message);
public sealed record ImportSummary(int Succeeded, IReadOnlyList<ImportFailure> Failed);
public interface IImportStore
{
/// <summary>
/// Ein Fehlschlag für ein einzelnes Element (Validierungsfehler, Duplikat,
/// falsches Format usw.) wird als ImportItemException geworfen.
/// Alles andere ist nicht „nur ein Problem dieses einen Elements“ und soll
/// unverändert nach außen durchgereicht werden.
/// </summary>
Task SaveAsync(ImportItem item, CancellationToken cancellationToken);
}
/// <summary>Ein Fehlschlag für ein einzelnes Element, der verworfen werden darf, um weiterzumachen.</summary>
public sealed class ImportItemException(string message, Exception? inner = null)
: Exception(message, inner);
public sealed class ImportRunner(IImportStore store, ILogger<ImportRunner> logger)
{
public async Task<ImportSummary> RunAsync(
IReadOnlyList<ImportItem> items,
CancellationToken cancellationToken)
{
int succeeded = 0;
List<ImportFailure> failed = [];
foreach (ImportItem item in items)
{
cancellationToken.ThrowIfCancellationRequested();
try
{
await store.SaveAsync(item, cancellationToken);
succeeded++;
}
catch (ImportItemException ex)
{
// Der Zustand für dieses eine Element lässt sich verwerfen,
// also aufzeichnen und mit dem nächsten Element weitermachen.
//
// Machen Sie hier kein catch (Exception) daraus. Würde man
// sogar NullReferenceException oder OutOfMemoryException als
// „bloß fehlerhafte Daten“ verschlucken, erreichte die
// unerwartete Ausnahme, für die in 4.3 „den Host mitsamt
// stoppen“ festgelegt wurde, die übergeordnete Schleife nicht
// mehr. Es würde weiter geschrieben, obwohl dem Zustand nicht
// mehr zu trauen ist
logger.LogError(ex, "Import failed. ItemId={ItemId}", item.Id);
failed.Add(new ImportFailure(item.Id, ex.GetType().Name, ex.Message));
}
}
return new ImportSummary(succeeded, failed);
}
}
Umgekehrt gehört dazu, dass die übergeordnete Schleife, die diese Verarbeitung antreibt, nichts abfängt und verschluckt. Wie in 4.3 beschrieben, ist der schlimmste Fall, dass die übergeordnete Schleife stirbt, während der Prozess allein bestehen bleibt, weshalb eine unerwartete Ausnahme hier zum Stoppen des Hosts führt.
// Seite der dauerhaft laufenden Schleife. Ein Stopp-Wunsch wird als
// Normalfall verlassen, jede andere unerwartete Ausnahme stoppt den Host mit.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
public interface IImportQueue
{
Task<IReadOnlyList<ImportItem>> DequeueBatchAsync(CancellationToken cancellationToken);
}
public sealed class ImportWorker(
IImportQueue queue,
ImportRunner runner,
IHostApplicationLifetime lifetime,
ILogger<ImportWorker> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
using PeriodicTimer timer = new(TimeSpan.FromSeconds(10));
while (await timer.WaitForNextTickAsync(stoppingToken))
{
IReadOnlyList<ImportItem> batch = await queue.DequeueBatchAsync(stoppingToken);
ImportSummary summary = await runner.RunAsync(batch, stoppingToken);
logger.LogInformation(
"Batch finished. Succeeded={Succeeded} Failed={Failed}",
summary.Succeeded,
summary.Failed.Count);
}
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
// Ein Stopp-Wunsch ist der Normalfall. Hier still enden.
}
catch (Exception ex)
{
// Die übergeordnete Schleife ist defekt = der Lebenszyklus des
// gesamten Workers ist defekt.
// Nicht verschlucken und einen Zustand erzeugen, in dem „nur der
// Prozess noch lebt“, sondern stoppen und dem Neustart übergeben.
logger.LogCritical(ex, "Worker loop failed unexpectedly. Stopping the host.");
// StopApplication ist eine Anforderung zum „sauberen Einklappen“.
// Allein damit lässt es sich von der Überwachungsseite, die nur
// bei einem Fehlschlag neu startet (Wiederherstellungsmaßnahmen
// eines Dienstes, Restart=on-failure bei systemd, die
// Neustartrichtlinie eines Containers), nicht von einem
// „ordnungsgemäß beendeten Auftrag“ unterscheiden, und der Worker
// steht nie wieder auf.
//
// Auch das bloße Setzen von Environment.ExitCode reicht nicht.
// Läuft die Anwendung als Windows-Dienst, meldet der Host bei
// sauberem Stopp SERVICE_STOPPED an den SCM, und ExitCode
// spiegelt sich nicht im Beendigungsstatus des Dienstes wider,
// sodass die Wiederherstellungsmaßnahme nicht greift. Beenden Sie
// den Prozess daher mit einem Exit-Code ungleich 0
Environment.Exit(1);
}
}
}
Entscheidend ist die Verwendung von Environment.Exit(1). IHostApplicationLifetime.StopApplication() ist eine Anforderung für einen normalen Stopp. Läuft die Anwendung als Windows-Dienst, wird dem SCM daher SERVICE_STOPPED gemeldet. Da sich der Environment.ExitCode des Prozesses nicht im Beendigungsstatus des Dienstes widerspiegelt, greift die in den Diensteigenschaften konfigurierte Wiederherstellungsmaßnahme („Dienst neu starten“) nicht. Auch das offizielle Tutorial zum Worker-Dienst hält ausdrücklich fest: Das voreingestellte BackgroundServiceExceptionBehavior.StopHost stoppt den Host „sauber“, weshalb die Windows-Dienstverwaltung ihn nicht neu startet; um die Wiederherstellungsmaßnahme greifen zu lassen, muss Environment.Exit mit einem Exit-Code ungleich 0 aufgerufen werden (siehe Quellen in Kapitel 11).
Environment.Exit beendet den aktuellen Prozess, also schreiben Sie alle Logs, die Sie vor dem Absturz haben möchten, unbedingt vor dieser Zeile fertig. Verwenden Sie einen puffernden Logger, fügen Sie ein Flush ein. Läuft die Anwendung dagegen nicht als Windows-Dienst, sondern nur unter systemd oder in einem Container, lässt sich die Überwachungsseite auch behandeln, indem Sie Environment.ExitCode setzen und dann mit StopApplication() einklappen – sie erkennt das ebenfalls als Fehlschlag. Wählen Sie je nach Umgebung, in der die Anwendung läuft.
Beachten Sie außerdem, dass sich die Rolle von catch innen und außen unterscheidet: innen die Aufzeichnung der Fehlereinheit, außen die Beendigung des Lebenszyklus. Vertauscht man das, stürzt entweder die Anwendung wegen eines einzelnen Fehlschlags ab, oder umgekehrt bleibt der Prozess bestehen, obwohl der Worker tot ist.
9.2 Erwartete und unerwartete Ausnahmen trennen
- Erwartet: Validierung, Nicht gefunden, Timeout, Abbruch, Verstoß gegen Geschäftsregeln
- Unerwartet: Gebrochene Annahmen, Entweichen aus der übergeordneten Schleife, Störungen an nativen Grenzen, Verdacht auf Speicherbeschädigung
9.3 Gemeinsamen Zustand klein halten
Je größer der gemeinsame, veränderliche Zustand ist, desto schwieriger wird die Entscheidung über das Fortsetzen. Umgekehrt gilt: Je mehr sich in einen Bildschirm, eine Sitzung, einen Worker einschließen lässt, desto leichter lässt sich auch der Fehler eingrenzen.
9.4 Gefährliche Verarbeitung in einen separaten Prozess auslagern
Bei COM / ActiveX / Drittanbieter-SDKs / unsafe / aufwendiger Bildverarbeitung / externer Gerätesteuerung – überall dort, wo sich der Schaden bei einem Absturz nicht ausbreiten soll – bringt eine Auslagerung in einen separaten Prozess erheblich viel.
9.5 Unbehandelte-Ausnahme-Handler dienen der „Aufzeichnung“, nicht der „Wiederherstellung“
- Ausnahmeinformationen
- Verarbeitungskontext
- Wichtige Logeinträge unmittelbar davor
- Konfiguration / Version / Verbindungsziele
- Weg zur Dump-Erfassung
Diese Punkte bereitzustellen und eine Form zu bevorzugen, mit der sich nach dem Absturz weiter ermitteln lässt, führt im Ergebnis zu mehr Stabilität.
In WPF reicht für den Aufzeichnungs-Handler etwa diese Form aus.
// App.xaml.cs in WPF (.NET 8). Der Handler dient der „Aufzeichnung“, nicht der „Wiederherstellung“.
using System;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
namespace SampleApp;
public partial class App : Application
{
private static readonly string CrashLogPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"SampleApp",
"crash.log");
protected override void OnStartup(StartupEventArgs e)
{
// Die Handler-Registrierung steht vor base.OnStartup(e).
// base.OnStartup löst das Startup-Ereignis aus. Tritt dort auf der
// Abonnentenseite eine Ausnahme auf, ist der weiter unten
// geschriebene Handler noch nicht registriert, und es entsteht der
// schlimmste Fall: „beim Start abgestürzt, aber keine einzige
// Logzeile vorhanden“. Da gerade der Fehlschlag des Startvorgangs
// selbst aufgezeichnet werden soll, wird der Handler vorher gesetzt.
// Auf dem UI-Thread unbehandelt aufgestiegene Ausnahme
DispatcherUnhandledException += (_, args) =>
{
Record("DispatcherUnhandledException", args.Exception);
// Mit args.Handled = true ließe sich fortfahren, aber ob das
// vertretbar ist, entscheidet sich nach den Bedingungen aus
// Kapitel 5. Ist das nicht eindeutig zu beurteilen, aufzeichnen
// und dem Standardverhalten (Beenden) überlassen.
args.Handled = false;
};
// Letzte Benachrichtigung, auch außerhalb des UI-Threads. Hier lässt
// sich nichts mehr aufhalten, daher nur zur Aufzeichnung.
AppDomain.CurrentDomain.UnhandledException += (_, args) =>
Record("AppDomain.UnhandledException", args.ExceptionObject as Exception);
// Ausnahme eines Task, der ohne await eingesammelt wurde
TaskScheduler.UnobservedTaskException += (_, args) =>
{
Record("UnobservedTaskException", args.Exception);
args.SetObserved();
};
// Erst wenn alle Handler gesetzt sind, mit dem Standardstartvorgang
// (Auslösen des Startup-Ereignisses) fortfahren
base.OnStartup(e);
}
private static void Record(string source, Exception? exception)
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(CrashLogPath)!);
string version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "unknown";
string text = string.Join(
Environment.NewLine,
$"[{DateTimeOffset.Now:O}] {source}",
$"version={version} os={Environment.OSVersion} user={Environment.UserName}",
exception?.ToString() ?? "(no exception object)",
string.Empty);
File.AppendAllText(CrashLogPath, text);
}
catch
{
// Schlägt die Aufzeichnung fehl, den Beendigungsvorgang trotzdem nicht aufhalten.
}
}
}
Wichtig ist, im Handler nicht zu versuchen, den Zustand zu reparieren. Hier soll nur Material hinterlassen werden, mit dem sich dasselbe Phänomen später nachverfolgen lässt.
9.6 Den unbehandelten Ausnahmeereignissen von WPF / WinForms nicht zu viel vertrauen
In WPF lässt sich mit Handled = true in DispatcherUnhandledException nach einer unbehandelten Ausnahme technisch fortfahren.
Auch in Windows Forms lässt sich auf dem Haupt-UI-Thread über Application.ThreadException und die Einstellung SetUnhandledExceptionMode wählen, wie angehalten wird.
Ob sich damit fortsetzen lässt, und ob die Bedingungen für eine Wiederherstellung erfüllt sind, sind aber zwei verschiedene Fragen.
9.7 Environment.FailFast nur dann, wenn „Aufräumen gefährlicher ist“
Das in der Flussdiagramm aus 3.1 auftauchende FailFast steht für Environment.FailFast. Das offizielle Dokument beschreibt folgendes Verhalten:
- Der Prozess wird beendet, ohne laufende
try/finallyoder Finalizer auszuführen - Unter Windows wird die übergebene Nachricht in das Windows-Anwendungsereignisprotokoll geschrieben, und es wird ein Anwendungsdump erstellt, bevor der Prozess endet
- Nachricht und Ausnahmeinformationen fließen über die Windows-Fehlerberichterstattung auch in die Fehlerberichte an Microsoft ein
- Wird die Methode unter dem Visual-Studio-Debugger aufgerufen, entsteht eine
ExecutionEngineException, und der Managed-Debugging-Assistent für fatalExecutionEngineError greift
Dass finally nicht ausgeführt wird, ist kein Nachteil, sondern der Zweck dieser API. Läuft Aufräumcode, während der Zustand beschädigt ist, kann er den beschädigten Inhalt unverändert in eine Datei oder Datenbank schreiben. Auch die Dokumentation beschreibt: Ist der Zustand der Anwendung nicht mehr reparierbar beschädigt und würde die Ausführung von try / finally oder Finalizern Ressourcen beschädigen, sollte statt Environment.Exit FailFast verwendet werden.
Die Wahl zwischen beiden sieht so aus:
| Situation | Wahl |
|---|---|
| Eine Invariante ist gebrochen, und Aufräumcode auszuführen wäre gefährlicher | Environment.FailFast |
| Der Zustand ist gesund, und Sie möchten nach dem Aufräumen beenden | Normaler Beendigungsvorgang (z. B. IHostApplicationLifetime.StopApplication) |
| Sie möchten nur einen Exit-Code zurückgeben und beenden | Environment.Exit oder ein return aus Main |
Im Code sieht der Einsatz zum Beispiel so aus:
// C# / .NET 8. Stelle, an der erkannt wird, dass eine Invariante des
// gemeinsamen Zustands gebrochen ist.
// Ab hier ist keiner Aufräumroutine mehr zu trauen.
if (cache.Count != store.Count)
{
Environment.FailFast(
$"Invariant broken: cache={cache.Count} store={store.Count}",
new InvalidOperationException("Cache and store are out of sync."));
}
Da der Dump automatisch erfasst wird, erleichtert es die spätere Untersuchung erheblich, wenn Sie in die an FailFast übergebene Nachricht schreiben, welche Invariante bei welchen Werten gebrochen war. Umgekehrt ist es übertrieben, FailFast bei einem erwarteten Fehlschlag wie einem Eingabefehler oder einem Kommunikationsfehler aufzurufen. Dafür gilt wieder das in 9.1 zur Fehlereinheit Gesagte.
10. Zusammenfassung
Bei einer unerwarteten Ausnahme geht es nicht um die Frage „Lässt sich diese Ausnahme abfangen?“, sondern darum, ob dem Zustand der Anwendung danach noch vertraut werden kann.
Als Entscheidungsreihenfolge reicht meist Folgendes aus:
- Lässt sich die fehlgeschlagene Einheit verwerfen?
- Lässt sich der gemeinsame Zustand zurücksetzen oder neu aufbauen?
- Lassen sich externe Nebenwirkungen erklären?
- Lässt sich der Gesundheitszustand von Speicher / Threads / nativen Grenzen vertrauen?
Sind Sie sich bei diesen vier Punkten sicher, können Sie fortsetzen. Sind Sie sich nicht sicher, ist Beenden vorzuziehen.
Besonders bei lange laufenden Anwendungen, Überwachungsanwendungen, Diensten und Gerätekopplung gibt es viele Situationen, in denen beschädigt weiterzuleben gefährlicher ist als ehrlich abzustürzen.
Ausnahmebehandlung ist nicht die Kunst, niemals abzustürzen. Es ist die Gestaltung, bei der Schäden klein bleiben, die Anwendung im Schadensfall ehrlich anhält und die Wiederherstellung leichtfällt.
11. Quellen
- .NET: Best practices for exceptions
- .NET: Create a Windows Service using BackgroundService (dazu, dass das voreingestellte
BackgroundServiceExceptionBehavior.StopHostden Host sauber stoppt, weshalb die Windows-Dienstverwaltung ihn nicht neu startet, und dass ein Aufruf vonEnvironment.Exitmit einem Exit-Code ungleich 0 nötig ist, damit die Wiederherstellungsmaßnahme greift) - .NET: System.Exception
- .NET: StackOverflowException
- .NET: System.AccessViolationException
- .NET: Environment.FailFast
- .NET: AppDomain.UnhandledException
- WPF: Application.DispatcherUnhandledException
- Windows Forms: Application.SetUnhandledExceptionMode
- .NET: Exceptions in Managed Threads
- .NET: TaskScheduler.UnobservedTaskException
- Windows: NTSTATUS Values - MS-ERREF
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
Wo sollten catch und Logging in der Ausnahmebehandlung stehen?
Um breite catch-Blöcke in tiefen Helpern, doppelte Logs in jeder Schicht und eine Ergebnisumwandlung, die die Ursache verschleiert, zu ve...
Eine Mindest-Sicherheitscheckliste für die Windows-Anwendungsentwicklung
Ordnet für WPF / WinForms / WinUI / C++ / C#-Businessanwendungen die Grundlagen zu Rechten, Signatur, Updates, Geheimnissen, HTTPS, Einga...
Code-Design für Geschäftssysteme — Produkt- und Kundencodes festlegen, und Prüfziffern
Ein praxisnaher Leitfaden zur Festlegung des Codesystems eines Geschäftssystems, einschließlich Produkt- und Kundencodes. Behandelt eine ...
Eine Einführung in ADRs (Architecture Decision Records) — Der minimale Weg, „warum wir es so entworfen haben“ in einem kleinen Team festzuhalten
Code erklärt nie, warum er so geschrieben wurde. Wir zeigen, wie Sie mit einem ADR (Architecture Decision Record) — eine Entscheidung, ei...
Wenn man eine Windows-App besser nicht ins Web migriert: Entscheidungstabelle und die pragmatische Lösung der Aufteilung
Der Wunsch, interne Windows-Anwendungen ins Web zu migrieren, nimmt stetig zu. Doch bei Apps mit Gerätekopplung, lokaler Dateiverarbeitun...
Verwandte Themen
Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.
Technische Windows-Themen
Portal zu Windows-Entwicklung, Fehleranalyse und der Nutzung bestehender Assets.
Leistungen zu diesem Thema
Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.
Technische Beratung und Design-Review
Es geht darum, die Leitlinien zur Ausnahmebehandlung, die Fehlergrenzen, die Neustartstrategie und die Entscheidungskriterien für Fortsetzen oder Beenden zu ordnen – ein Thema, das gut zur technischen Beratung und zum Design-Review passt.
Fehleruntersuchung und Ursachenanalyse
Der Ablauf, nach einer unerwarteten Ausnahme zwischen Fortsetzen und Beenden zu unterscheiden – einschließlich Zustandsbeschädigung und externer Nebenwirkungen –, lässt sich gut im Rahmen der Fehleruntersuchung und Ursachenanalyse angehen.
Häufige Fragen
Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.
- Darf man eine unerwartete Ausnahme nicht einfach mit catch (Exception) abfangen und weitermachen?
- Nur ein Log auszugeben und weiterzumachen ist in der Regel gefährlich. Das verdeckt die Ursache und verlängert leicht das Leben eines beschädigten Zustands. Fortsetzen ist nur dann vertretbar, wenn drei Dinge zusammenkommen: Sie können die fehlgeschlagene Einheit verwerfen, Sie können den gemeinsamen Zustand zurücksetzen, und Sie können externe Nebenwirkungen erklären. Die entscheidende Frage lautet nicht „Lässt sich diese Ausnahme abfangen?“, sondern „Kann ich dem Zustand der Anwendung danach noch vertrauen?“
- Bei welchen Ausnahmen sollte man sofort beenden?
- Bei Ausnahmen wie StackOverflowException, AccessViolationException oder einer schwerwiegenden OutOfMemoryException, die die Gesundheit des gesamten Prozesses infrage stellen, ist es sicherer, nicht von einer Fortsetzung auszugehen. Bei StackOverflowException ist der Aufrufstapel zusammengebrochen, bei AccessViolationException liegt ein unzulässiger Zugriff auf geschützten Speicher vor, der eine Speicherbeschädigung vermuten lässt. Auch Störungen, die ihren Ursprung in COM-, P/Invoke- oder Callbacks von Drittanbieter-SDKs haben, tendieren stark zum Beenden, weil sich die Sicherheit allein von der Managed-Seite aus schwer beurteilen lässt.
- Unter welchen Bedingungen darf die Anwendung nach einer Ausnahme fortgesetzt werden?
- Voraussetzung ist, dass im Wesentlichen folgende Bedingungen zusammen erfüllt sind: Die fehlgeschlagene Einheit ist eindeutig (eine Operation, ein Bildschirm, ein Job, eine Verbindung – Sie wissen, was verworfen wird), der Zustand lässt sich verwerfen und neu aufbauen, die Verunreinigung breitet sich nicht auf den gemeinsamen Zustand aus, externe Nebenwirkungen lassen sich erklären, dem Benutzer kann ehrlich mitgeteilt werden, dass „dieser Vorgang fehlgeschlagen ist“, und Logs oder Metriken ermöglichen eine nachträgliche Untersuchung. Ist die Verarbeitungsgrenze klar – etwa eine einzelne UI-Operation oder ein einzelner Importjob –, kann fortgesetzt werden. Bei einer unvollständigen Aktualisierung des gemeinsamen Zustands, einer defekten übergeordneten Schleife, Startvorgängen oder Störungen an nativen Grenzen ist dagegen das Beenden vorzuziehen.
- Reicht es, in WPF bei DispatcherUnhandledException Handled=true zu setzen, um fortzufahren?
- Nach einer unbehandelten Ausnahme fortzufahren ist damit technisch möglich, aber ob man fortfahren kann und ob es sicher ist fortzufahren, sind zwei verschiedene Fragen. Handler wie AppDomain.UnhandledException oder DispatcherUnhandledException sind als letzte Aufzeichnungsstelle nützlich, aber kein magischer Wiederherstellungspunkt. Es ist stabiler, Ausnahmeinformationen, Verarbeitungskontext und einen Weg zur Dump-Erfassung bereitzuhalten und die Untersuchung nach dem Absturz zu priorisieren. Besonders bei lange laufenden Diensten und Überwachungsanwendungen ist es oft sicherer und leichter zu diagnostizieren, abzustürzen und neu zu starten, als halb kaputt weiterzulaufen.
Autorenprofil
Profilseite des Artikelautors.
Go Komura
Geschäftsführer von KomuraSoft LLC
Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.