Windows-Herunterfahren aus Sicht Ihrer App — Beendigungsbenachrichtigungen, Neustarts und Stromverlust richtig überstehen

· · Windows, Herunterfahren, Windows-Entwicklung, Windows-Dienste, Geräte-PCs, Datenintegrität, Dauerbetrieb, UPS

„Nach einem nächtlichen Windows-Update-Neustart ist die Mess-App auf dem Geräte-PC mitten im Schreiben ausgefallen, und am Morgen war die Messdatei beschädigt.“ „Jemand hat sich von einem gemeinsam genutzten PC abgemeldet und sich beschwert, dass ungespeicherte Änderungen verschwunden waren.“ — Für lang laufende Windows-Apps sind diese zwei Beratungen Klassiker.

Gemeinsam ist beiden Standorten, Herunterfahren als „ein anormales Ereignis, das nicht passieren sollte“ zu behandeln. In Wirklichkeit kommen allerdings von automatischen Windows-Update-Neustarts, Benutzerabmeldung und einem von einer USV ausgelösten Herunterfahren bis zu einem unangekündigten Stromverlust Ereignisse, die die Ausführung von außerhalb der App kappen, früher oder später. Sie können nicht verhindern, dass sie kommen. Was Sie verhindern können, ist „Daten zu verlieren, wenn sie kommen“.

Glücklicherweise hat Windows einen Mechanismus, der die App vor dem Herunterfahren benachrichtigt, für GUI-Apps, Konsolen-Apps und Dienste gleichermaßen. An IT-Personal in kleinen und mittleren Unternehmen und an Windows-App-Entwickler (besonders von Geräte-PC- und lang laufenden Apps) gerichtet, ordnet dieser Artikel, wie Sie diese Benachrichtigungen empfangen, wie Sie Aufräumen entwerfen, das „in wenigen Sekunden fertig ist“, automatische Wiederherstellung nach einem Neustart und wie Sie sich auf einen Stromverlust vorbereiten, der keine Benachrichtigung bringt — alles gestützt auf Microsoft-Learn-Primärquellen Stand August 2026.

1. Zuerst das Fazit

  • Entwerfen Sie Herunterfahren als „ein normales Ereignis, das früher oder später kommt“. Die Zeit, die Sie nach Empfang der Benachrichtigung nutzen können, beträgt grundsätzlich nur etwa 5 Sekunden, ein Entwurf, der alles auf der Stelle fieberhaft speichert, bricht also zusammen. Die Voraussetzung ist häufiges automatisches Speichern, sodass „das Delta, das beim Herunterfahren gespeichert werden muss“ klein bleibt.1
  • Auf Client-Betriebssystemen ab Windows 8 ist „Herunterfahren“, wenn Schnellstart aktiviert ist (der Standard auf den meisten PCs, die Ruhezustand unterstützen), ein Hybrid-Herunterfahren, und der Kernel geht nur in den Ruhezustand. Das Einzige, das vollständig zurückgesetzt wird, ist „Neu starten“. Das ist der wahre Grund für „ich habe es heruntergefahren und es wurde nicht besser, dann habe ich neu gestartet und es wurde besser“.2
  • Eine GUI-App sollte TRUE sofort auf WM_QUERYENDSESSION zurückgeben und Aufräumen in WM_ENDSESSION machen. Grundsätzlich dürfen Sie nicht FALSE zurückgeben (ablehnen).1
  • Nur wenn Sie wirklich eine Operation haben, die nicht unterbrochen werden kann, sollten Sie eine Begründung mit ShutdownBlockReasonCreate anzeigen. Selbst dann können Benutzer und Betriebssystem die Fortsetzung erzwingen, ein Entwurf, der „wir können blockieren“ voraussetzt, hält also nicht.34
  • Eine Konsolen-App empfängt die Benachrichtigung mit SetConsoleCtrlHandler. Die Schonfrist ist noch kürzer — ein Standard von 5 Sekunden für das Schließen der Konsole. Es gibt auch eine Falle: In einem Prozess, der gdi32.dll oder user32.dll geladen hat, kommen einige dieser Ereignisse nicht an.56
  • Aufräumen, das sich auf .NETs AppDomain.ProcessExit stützt, läuft ab .NET 10 auf Pfaden, auf denen der Prozess „von außen beendet“ wird, nicht. Bei einem normalen Beenden wie der Rückkehr aus Main läuft es noch wie zuvor, aber weil die Laufzeit keine Standardbehandlung mehr für Beendigungssignale wie Konsolenschließen und Herunterfahren bereitstellt, muss Aufräumen auf diesen Pfaden zur Benachrichtigung wechseln, die zum App-Modell passt.7
  • Ein Windows-Dienst kann SERVICE_ACCEPT_PRESHUTDOWN früher und mit einer konfigurierbaren Schonfrist empfangen als SERVICE_ACCEPT_SHUTDOWN (etwa 20 Sekunden Schonfrist). Das Standard-PRESHUTDOWN-Timeout wurde allerdings ab Windows 10 Creators Update auf 10 Sekunden verkürzt, Sie brauchen also in jedem Fall einen Entwurf, der sich nicht zu sehr auf die Schonfrist stützt.89
  • Automatische Wiederherstellung nach einem Neustart lässt sich durch die Kombination von RegisterApplicationRestart mit ARSO (automatische Anmeldung) erreichen. Wiederherstellungspfade sind für Absturz, „Keine Rückmeldung“ und updategetriebenen Neustart vorgesehen.1011
  • Ein Stromverlust bringt überhaupt keine Benachrichtigung. Das Standardmuster ist, vollständig in eine temporäre Datei zu schreiben, zu leeren und mit ReplaceFile zu tauschen, aber weil ReplaceFile Atomizität über einen Stromverlust hinweg ebenfalls nicht garantiert, gehört ein Wiederherstellungspfad aus Sicherung (.bak) plus Prüfung zur Ladezeit zum Satz. Nachträgliche Isolation kann aus dem Ereignisprotokoll (1074/41/6008) erfolgen.121314

In einem Satz ist die Schlussfolgerung dieses Artikels: „halten Sie immer einen Zustand, aus dem Sie den Laden in wenigen Sekunden schließen können, wenn die Benachrichtigung ankommt, und schreiben Sie so, dass es auch bei einem Stromverlust ohne Benachrichtigung nicht kaputtgeht“.

2. Was beim Herunterfahren passiert — Vier Wege zu enden

2.1. Abmelden, Herunterfahren, Neu starten und Stromverlust

Aus Sicht der App zählen zwei Achsen: „wie die Benutzersitzung endet“ und „was mit dem Kernel passiert“.

Vorgang Benutzersitzung Kernel und Treiber Benachrichtigung an die App
Abmelden Endet Läuft weiter WM_QUERYENDSESSION (ENDSESSION_LOGOFF) → WM_ENDSESSION
Herunterfahren (Schnellstart aktiviert) Endet Geht in den Ruhezustand (in hiberfil.sys gespeichert) WM_QUERYENDSESSION → WM_ENDSESSION, (PRE)SHUTDOWN an Dienste
Neu starten Endet Endet vollständig; der nächste Boot ist ein vollständiger Boot Wie oben
Stromverlust Verschwindet sofort Verschwindet sofort Keine

Abmelden und Herunterfahren sind aus Sicht der App fast dasselbe Ereignis. Wenn das Bit ENDSESSION_LOGOFF in lParam von WM_QUERYENDSESSION gesetzt ist, ist es eine Abmeldung; wenn es 0 ist, ist es ein Herunterfahren oder ein Neustart (die zwei können Sie nicht unterscheiden).1 Mit anderen Worten: Die Selbstzufriedenheit von „es ist nur eine Abmeldung, wir kommen klar“ hält nicht, und der richtige Entwurf ist, dass derselbe Aufräumcode aufgerufen wird.

Vier Wege zu enden und die Benachrichtigung an die AppAbmelden, Herunterfahren und Neu starten liefern die Benachrichtigung WM_QUERYENDSESSION zu WM_ENDSESSION, und Aufräumen endet in wenigen Sekunden. Nur ein Stromverlust hat überhaupt keine Benachrichtigung, Sie bereiten also mit dem Schreibentwurf in Kapitel 8 und einer USV vorAbmeldenQUERY → ENDSESSIONHerunterfahrenNeu startenStromverlustKeine Benachrichtigung: Schreiben + USVAufräumen in Sekunden

Abbildung 1: Abmelden, Herunterfahren und Neu starten liefern die Benachrichtigung WM_QUERYENDSESSION zu WM_ENDSESSION, und Aufräumen endet in wenigen Sekunden. Nur ein Stromverlust hat überhaupt keine Benachrichtigung, Sie bereiten also mit dem Schreibentwurf in Kapitel 8 und einer USV vor.

2.2. Der wahre Grund für „ich habe es heruntergefahren und es wurde nicht besser“ — Hybrid-Herunterfahren

Die leicht zu übersehende Zeile ist die zweite in der Tabelle. Auf Client-Betriebssystemen ab Windows 8 ist Schnellstart (Hybrid-Herunterfahren) standardmäßig auf PCs aktiviert, die Ruhezustand unterstützen, und das Verhalten von „Herunterfahren“ hat sich geändert. Die Abmeldung der Benutzersitzung geschieht noch wie üblich, aber die Kernelsitzung wird nicht geschlossen; sie wird, Gerätetreiber und alles, in die Ruhezustandsdatei (hiberfil.sys) gespeichert und beim nächsten Boot unverändert wiederhergestellt. Das macht den Start schneller, aber Kernel- und Treiberzustand überleben selbst nach dem Abschalten der Stromversorgung.2 Das ist allerdings bedingtes Verhalten. In einer Umgebung, in der Ruhezustand selbst deaktiviert ist (powercfg /hibernate off), in der Richtlinie oder Energieoptionen Schnellstart ausgeschaltet haben, und auf Windows Server ist Herunterfahren ein konventionelles vollständiges Herunterfahren. Sie können erkennen, auf welche Weise ein gegebener PC läuft, anhand des Kontrollkästchens „Schnellstart aktivieren“ in den Energieoptionen oder anhand dessen, ob powercfg /a (verfügbare Energiesparzustände) „Fast Startup“ listet.

Was mit dem Kernel bei einem Herunterfahren-Vorgang passiertEin Herunterfahren-Vorgang teilt sich in ein vollständiges Herunterfahren oder Kernel-Ruhezustand, je nachdem ob Schnellstart an ist, und Neu starten macht immer einen vollständigen BootSchnellstart anRuhezustand aus / ServerHerunterfahrenNeu startenSitzung endet + Kernel-RuhezustandVollständiges HerunterfahrenAls Nächstes: Kernel wiederherstellenAls Nächstes: vollständiger Boot

Abbildung 2: Ein Herunterfahren-Vorgang teilt sich in ein vollständiges Herunterfahren oder Kernel-Ruhezustand, je nachdem ob Schnellstart an ist, und Neu starten macht immer einen vollständigen Boot.

„Neu starten“ führt dagegen immer einen vollständigen Bootzyklus aus. Nach einem Treiberupdate brauchen Sie zum Beispiel einen vollständig neuen Zustand.2 Daraus fallen mehrere Phänomene, die Sie im Feld hören, an ihren Platz.

  • „Ich habe es heruntergefahren und wieder eingeschaltet, aber das Geräteproblem ging nicht weg“ — Kernel und Treiber wurden nur aus dem Ruhezustand wiederhergestellt; sie wurden nicht zurückgesetzt
  • „Es wurde besser, nachdem ich neu gestartet habe“ — weil ein vollständiger Boot sie initialisiert hat
  • Vorfallprozeduren für Geräte-PCs sollten „Neu starten“ sagen, nicht „aus- und einschalten“

Wenn Sie ein vollständiges Herunterfahren von der Kommandozeile ausdrücklich machen wollen, shutdown /s (der Standard von Shutdown.exe ist ein vollständiges Herunterfahren); wenn Sie das standardmäßige Hybridverhalten wollen, shutdown /s /hybrid.2 Schnellstart zu deaktivieren wird nicht empfohlen. Die App-Seite sollte annehmen „beim Herunterfahren geht der Kernel möglicherweise nur in den Ruhezustand“ — zum Beispiel „kumulierte Laufzeit“ nicht aus der Betriebssystem-Bootzeit schätzen — und so entwerfen, dass es auf keine der beiden Weisen kaputtgeht (ob Schnellstart an oder aus ist, unterscheidet sich nach Umgebung).

3. Wie sich eine GUI-App verhalten sollte — WM_QUERYENDSESSION und WM_ENDSESSION

3.1. Wie die zwei Nachrichten die Arbeit aufteilen

Eine App, die ein Fenster und eine Nachrichtenwarteschlange hat, wird in zwei Stufen über das Sitzungsende benachrichtigt.1

  1. WM_QUERYENDSESSION — eine Abfrage: „ist es in Ordnung zu enden?“ Die App sollte TRUE sofort zurückgeben; die Standardantwort von DefWindowProc ist ebenfalls TRUE. Beginnen Sie hier nicht mit Aufräumen.
  2. WM_ENDSESSION (wParam=TRUE) — eine festgelegte Benachrichtigung: „die Sitzung endet wirklich“. Aufräumen geschieht hier.

FALSE auf WM_QUERYENDSESSION zurückzugeben kann das Herunterfahren abbrechen, aber die Dokumentation ist ausdrücklich, dass „Sie TRUE zurückgeben und die Absicht des Benutzers respektieren sollten“, und eine App, die FALSE zurückgegeben hat, wird trotzdem in der Vollbild-UI als „eine App, die das Herunterfahren verhindert“ gezeigt. Konsolen-Apps und Apps ohne sichtbares Fenster können das Herunterfahren von vornherein nicht abbrechen, und wenn sie nicht innerhalb von 5 Sekunden antworten, werden sie automatisch beendet.14

Ablauf der zweistufigen Sitzungsende-BenachrichtigungTRUE auf die WM_QUERYENDSESSION-Abfrage zurückzugeben legt mit WM_ENDSESSION fest und Aufräumen läuft. Ablehnen mit FALSE zeigt die App als eine, die das Herunterfahren verhindert, und etwa 5 Sekunden ohne Antwort können die Fortsetzung erzwingenTRUE(Regel)FALSE(ablehnen)Keine Antwort ~5 sFortsetzung erzwingenAbbrechenWM_QUERYENDSESSIONWM_ENDSESSION(festgelegt)Als blockierend angezeigtAls hängend behandeltAufräumen hierProzessendeHerunterfahren abgebrochen

Abbildung 3: TRUE auf die WM_QUERYENDSESSION-Abfrage zurückzugeben legt mit WM_ENDSESSION fest und Aufräumen läuft. Ablehnen mit FALSE zeigt die App als eine, die das Herunterfahren verhindert, und etwa 5 Sekunden ohne Antwort können die Fortsetzung erzwingen.

3.2. Was passiert, wenn Sie nicht antworten — die 5-Sekunden-Mauer

Sowohl bei WM_QUERYENDSESSION als auch bei WM_ENDSESSION können Sie die Antwort um etwa 5 Sekunden verzögern. Darüber hinaus zeigt das System den Bildschirm „Diese App verhindert das Herunterfahren“, und der Benutzer kann wählen, die Fortsetzung zu erzwingen (= die App zwangsweise zu beenden).4 Einem zwangsbeendeten Prozess wird keine weitere Chance gegeben, sein Speichern zu beenden.

Die Entwurfspunkte sind daher diese zwei.

  • Halten Sie Aufräumen auf eine Menge, die innerhalb von 5 Sekunden fertig ist. Microsoft selbst empfiehlt, Daten im gewöhnlichen Betrieb häufig zu speichern, sodass weniger beim Herunterfahren gespeichert werden muss, und ungespeicherte Daten an einem temporären Ort zu speichern, um sie beim nächsten Start wiederherzustellen.1
  • Stellen Sie während des Herunterfahrens keinen Bestätigungsdialog. Während Sie auf „Möchten Sie speichern?“ warten, vergehen die 5 Sekunden. Fallen Sie still auf die sichere Seite (automatisches Speichern).

3.3. Implementierung in WinForms und WPF

In einer .NET-Desktop-App werden diese Nachrichten in Framework-Ereignisse übersetzt. In WinForms wird FormClosing ausgelöst, und CloseReason sagt Ihnen, ob Herunterfahren die Ursache ist.

// WinForms: FormClosing is also raised on shutdown / sign-out
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
    if (e.CloseReason == CloseReason.WindowsShutDown)
    {
        // Do only an idempotent snapshot save. Do not show a dialog.
        // Do not set e.Cancel = true (refuse) either.
        SaveWorkingStateToTempFile();
        return;
    }

    // For ordinary closes such as the user clicking the × button, you may confirm here
}

In WPF entspricht das Ereignis Application.SessionEnding (das XAML-Attribut SessionEnding oder eine OnSessionEnding-Überschreibung).

Wie WinForms/WPF-Ereignisse den Nachrichten entsprechenDie Abfragephase von WM_QUERYENDSESSION entspricht WinForms FormClosing und WPF SessionEnding, und was Sie dort tun, ist höchstens eine idempotente Schnappspeicherung. Für das festgelegte WM_ENDSESSION gibt es kein entsprechendes Ereignis, empfangen Sie es also in WndProc oder einem Hook und machen Sie Aufräumen, das nur nach dem Commit laufen kannWM_QUERYENDSESSIONWinForms: FormClosingWPF: SessionEndingNur idempotenter SchnappschussWM_ENDSESSIONKein Ereignis: WndProc-HookAufräumen nach Commit

Abbildung 4: Die Abfragephase von WM_QUERYENDSESSION entspricht WinForms FormClosing und WPF SessionEnding, und was Sie dort tun, ist höchstens eine idempotente Schnappspeicherung. Für das festgelegte WM_ENDSESSION gibt es kein entsprechendes Ereignis, empfangen Sie es also in WndProc oder einem Hook und machen Sie Aufräumen, das nur nach dem Commit laufen kann.

// WPF: App.xaml.cs
protected override void OnSessionEnding(SessionEndingCancelEventArgs e)
{
    base.OnSessionEnding(e);

    // You can distinguish ReasonSessionEnding.Logoff / Shutdown,
    // but the baseline is to run the same snapshot save in either case
    SaveWorkingStateToTempFile();

    // Do not set e.Cancel = true unless you have an exceptional reason
}

Hier gibt es eine Vorsicht. Sowohl FormClosing (CloseReason.WindowsShutDown) als auch WPFs SessionEnding entsprechen der Abfragephase (WM_QUERYENDSESSION). Wenn eine andere App ablehnt, wird das Herunterfahren abgebrochen und Ihre App läuft weiter. Was Sie in diesen Ereignissen tun dürfen, ist daher eine idempotente Schnappspeicherung, die nicht schadet, wenn das Herunterfahren abgebrochen wird, und dasselbe Ergebnis liefert, egal wie oft sie läuft. Wenn Sie „Aufräumen brauchen, das nur gemacht werden darf, wenn wir wirklich enden“ (Trennen, Ressourcen zurückgeben und so weiter), haken Sie das festgelegte WM_ENDSESSION (wParam=TRUE) direkt in WndProc ein und tun Sie es dort.

Auf jedem der beiden Pfade falten Sie den Körper in eine gemeinsame „Schnappspeicherungs“-Funktion und schreiben Wiederherstellungsdaten für ein normales Beenden, ein Herunterfahren und (wenn möglich) einen Absturz im selben Format, sodass die Wiederherstellungslogik beim nächsten Start ein einziger Pfad ist. So zu entwerfen, dass auch bei einem Absturz Informationen bleiben, ist in „Design für Windows-Apps, das bei einem Absturz Protokolle und Dumps hinterlässt“ behandelt.

4. Wenn Sie wirklich blockieren müssen — ShutdownBlockReasonCreate

Operationen, die physisch kaputtgehen, wenn sie mittendrin gekappt werden, etwa das Schreiben einer CD oder von Firmware, sind die Ausnahme. Die richtige Praxis hier ist, eine Begründungszeichenfolge mit ShutdownBlockReasonCreate zu registrieren, wenn die ununterbrechbare Operation beginnt, und ShutdownBlockReasonDestroy sofort aufzurufen, wenn sie endet. Wenn Herunterfahren angefordert wird, wird diese Begründung auf dem Bildschirm „Diese App verhindert das Herunterfahren“ angezeigt, und der Benutzer kann entscheiden, ob fortgesetzt oder abgebrochen wird.3

Ablauf des Schutzes mit ShutdownBlockReasonCreateRegistrieren Sie eine Begründung, wenn die ununterbrechbare Operation beginnt; wenn während des Schutzes eine Herunterfahren-Anforderung ankommt, wird die Begründung im Vollbild gezeigt und WM_QUERYENDSESSION mit FALSE abgelehnt. Der Benutzer kann abbrechen oder die Fortsetzung erzwingen, und die Begründung wird gelöscht, wenn die Operation endetAbbrechenFortsetzung erzwingenUnunterbrechbare Arbeit startenShutdownBlockReasonCreateAuf einem Worker-Thread ausführenBeenden: DestroyHerunterfahren währenddessenBegründung zeigen + FALSEProzessende

Abbildung 5: Registrieren Sie eine Begründung, wenn die ununterbrechbare Operation beginnt; wenn während des Schutzes eine Herunterfahren-Anforderung ankommt, wird die Begründung im Vollbild gezeigt und WM_QUERYENDSESSION mit FALSE abgelehnt. Der Benutzer kann abbrechen oder die Fortsetzung erzwingen, und die Begründung wird gelöscht, wenn die Operation endet.

[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool ShutdownBlockReasonCreate(IntPtr hWnd, string pwszReason);

[DllImport("user32.dll", SetLastError = true)]
static extern bool ShutdownBlockReasonDestroy(IntPtr hWnd);

// Call from the thread that created the main window (it fails from other threads)
_criticalOperationInProgress = true;
ShutdownBlockReasonCreate(this.Handle, "Writing measurement data to a file");
try
{
    // Run the uninterruptible operation on a worker thread. If you run it
    // synchronously on the UI thread the message pump stops, and the process
    // is force-continued as "Not Responding" before the WM_QUERYENDSESSION
    // refusal code below can run
    await Task.Run(() => WriteMeasurementData());
}
finally
{
    ShutdownBlockReasonDestroy(this.Handle);
    _criticalOperationInProgress = false;
}

// Also, refuse WM_QUERYENDSESSION with FALSE only while protected
protected override void WndProc(ref Message m)
{
    const int WM_QUERYENDSESSION = 0x0011;
    if (m.Msg == WM_QUERYENDSESSION && _criticalOperationInProgress)
    {
        m.Result = IntPtr.Zero;   // Refuse. The registered reason string is shown in the full-screen UI
        return;
    }
    base.WndProc(ref m);
}

Das leichte Missverständnis hier ist die Rollenaufteilung. Alles, was ShutdownBlockReasonCreate tut, ist eine Begründungszeichenfolge zu registrieren; es hält das Herunterfahren selbst nicht an. Was das Herunterfahren tatsächlich zurückhält, ist Ihre eigene Behandlung, die FALSE auf WM_QUERYENDSESSION zurückgibt, solange ein Schutzflag gesetzt ist, wie oben. Nutzen Sie die zwei als Satz, und löschen Sie beide sofort, wenn die Operation endet. Führen Sie außerdem die geschützte Operation selbst auf einem Worker-Thread aus und halten Sie den UI-Thread in der Lage, Nachrichten zu verarbeiten — der Ablehnungsmechanismus funktioniert erst, sobald die Nachricht ankommt (und selbst dann können Benutzer und Betriebssystem die Fortsetzung erzwingen, ein Schreibentwurf, der nicht kaputtgeht „wenn es nicht angehalten hat“ — Kapitel 8 — ist also weiterhin erforderlich).

Es gibt drei betriebliche Vorsichten.

  • Halten Sie die Begründungszeichenfolge kurz und konkret. Der Benutzer hat Eile und wird nur wenige Sekunden lesen. Die Dokumentation selbst gibt „Burning a CD“ als angemessenes Beispiel.3
  • Lassen Sie sie nicht über die ganze Lebensdauer der App registriert. „Nur solange eine ununterbrechbare Operation läuft“ ist, was die API voraussetzt.
  • Entwerfen Sie nicht unter der Annahme, dass Sie blockieren können. Der Benutzer kann die Fortsetzung erzwingen, und ein erzwungenes Herunterfahren (ENDSESSION_CRITICAL) wartet von vornherein nicht. Die Dokumentation ist ausdrücklich: „Applications should not depend on being able to block shutdown“.4

5. Wie sich Konsolen-Apps und Hintergrundprozesse verhalten sollten

5.1. SetConsoleCtrlHandler und eine kurze Schonfrist

Eine Konsolen-App kann keine Fensternachrichten empfangen, Steuersignale kommen also bei einer mit SetConsoleCtrlHandler registrierten Handlerfunktion an. Die Standardschonfrist pro Signal ist wie folgt.5

Signal Wann es auftritt Standardschonfrist
CTRL_C_EVENT / CTRL_BREAK_EVENT Strg+C / Strg+Pause Kein Timeout
CTRL_CLOSE_EVENT Schließen der Konsole, Task-Manager „Task beenden“ (ein erzwungenes Prozess-Kill vom Register „Details“ ist ein sofortiges Beenden ohne Benachrichtigung und liegt außerhalb dieser Tabelle) Etwa 5 Sekunden
CTRL_SHUTDOWN_EVENT Systemherunterfahren (Dienstprozesse) Etwa 20 Sekunden

Es gibt zwei Punkte zu beachten. Erstens: Im Wesentlichen kann nur ein Prozess, der als Dienst läuft, CTRL_LOGOFF_EVENT und CTRL_SHUTDOWN_EVENT empfangen. Eine App in einer interaktiven Sitzung wird bei der Abmeldung beendet, ein Entwurf, der auf diese Signale wartet, hält also nicht.5 Zweitens: Ein Prozess, der gdi32.dll oder user32.dll geladen hat, wird als Windows-App behandelt, selbst wenn Sie ihn als Konsolen-App denken, und die LOGOFF/SHUTDOWN-Handler werden nicht aufgerufen. Die offizielle Umgehung ist, ein verstecktes Fenster zu erzeugen und WM_QUERYENDSESSION/WM_ENDSESSION zu empfangen.6

Schonfrist pro KonsolensignalStrg+C und Strg+Pause haben kein ausdrückliches Timeout; Konsolenschließen hat etwa 5 Sekunden und ein Herunterfahren-Signal an einen Dienstprozess etwa 20 Sekunden; das Überschreiten beendet den Prozess zwangsweiseKein TimeoutEtwa 5 sEtwa 20 sCTRL_C / BREAKHandlerRoutine-AufräumenCTRL_CLOSECTRL_SHUTDOWNZwangskill nach Schonfrist

Abbildung 6: Strg+C und Strg+Pause haben kein ausdrückliches Timeout; Konsolenschließen hat etwa 5 Sekunden und ein Herunterfahren-Signal an einen Dienstprozess etwa 20 Sekunden; das Überschreiten beendet den Prozess zwangsweise.

// Console app: clean up on Ctrl+C and console close
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add);

delegate bool HandlerRoutine(int ctrlType);   // 2 = CTRL_CLOSE_EVENT

static readonly HandlerRoutine s_handler = OnCtrlEvent;  // Keep a reference so GC does not collect it

static bool OnCtrlEvent(int ctrlType)
{
    // Do only cleanup that finishes within 5 seconds
    FlushAndCloseDataFile();
    return false;   // Proceed to the default handler; the process exits
}

static void Main()
{
    SetConsoleCtrlHandler(s_handler, add: true);
    // ...
}

5.2. Die .NET-Falle — verlassen Sie sich nicht auf ProcessExit

In .NET gibt es seit Langem ein Lagermuster von „einfach in AppDomain.ProcessExit aufräumen“, aber ab .NET 10 stellt die Laufzeit keine Standard-Beendigungssignalhandler mehr bereit, und weder ProcessExit noch AssemblyLoadContext.Unloading feuern bei CTRL_CLOSE_EVENT/CTRL_SHUTDOWN_EVENT. Der Standardhandler des Betriebssystems beendet den Prozess einfach sofort.7

Wie ProcessExit sich in .NET 10 geändert hatBis .NET 9 empfing der Standard-Signalhandler der Laufzeit das Beendigungssignal, löste ProcessExit aus und beendete dann. Ab .NET 10 stellt die Laufzeit keinen Standardhandler bereit, die Standardbehandlung des Betriebssystems beendet den Prozess sofort, und Sie registrieren selbst einen HandlerCTRL_CLOSE / SHUTDOWNBis .NET 9: ProcessExitAb .NET 10: sofortiges BeendenSelbst einen Handler registrieren

Abbildung 7: Bis .NET 9 empfing der Standard-Signalhandler der Laufzeit das Beendigungssignal, löste ProcessExit aus und beendete dann. Ab .NET 10 stellt die Laufzeit keinen Standardhandler bereit, die Standardbehandlung des Betriebssystems beendet den Prozess sofort, und Sie registrieren selbst einen Handler.

Stattdessen wechseln Sie zum kanonischen Pfad für jedes App-Modell.

  • GUI-App: FormClosing / SessionEnding aus dem vorherigen Kapitel
  • Generic Host (einschließlich Worker Service): IHostApplicationLifetime und BackgroundService.StopAsync. Machen Sie die Stopp-Schonfrist mit HostOptions.ShutdownTimeout ausdrücklich
  • Nackte Konsolen-App: SetConsoleCtrlHandler (oder abonnieren Sie SIGINT/SIGTERM-Äquivalente mit PosixSignalRegistration)
Wo jedes App-Modell die Beendigungsbenachrichtigung empfängtEine GUI-App nutzt FormClosing und SessionEnding plus einen WM_ENDSESSION-Hook für festgelegte Arbeit; Generic Host nutzt IHostApplicationLifetime und StopAsync; eine nackte Konsolen-App nutzt SetConsoleCtrlHandler oder PosixSignalRegistration. Sich auf ProcessExit zu verlassen, feuert auf Pfaden äußerer Signale nichtGUINicht GUIHostKonsoleWelches App-Modell?FormClosing / SessionEndingHost oder Konsole?ENDSESSION-HookLifetime + StopAsyncSetConsoleCtrlHandlerShutdownTimeout setzenNicht auf ProcessExit verlassen

Abbildung 8: Eine GUI-App nutzt FormClosing und SessionEnding plus einen WM_ENDSESSION-Hook für festgelegte Arbeit; Generic Host nutzt IHostApplicationLifetime und StopAsync; eine nackte Konsolen-App nutzt SetConsoleCtrlHandler oder PosixSignalRegistration. Sich auf ProcessExit zu verlassen, feuert auf Pfaden äußerer Signale nicht.

Die Schonfrist unterscheidet sich nach Pfad — etwa 5 Sekunden für GUI- und Konsolenschließen, die SCM-Schonfrist von Kapitel 6 für einen Dienst (etwa 20 Sekunden oder der konfigurierte Wert für PRESHUTDOWN) und kein ausdrückliches Timeout für Strg+C. Auf jedem Pfad ist die Schonfrist allerdings begrenzt und nicht verlässlich, die Entwurfsachse ist also, dass der Normalfall „bereits an jedem Verarbeitungsprüfpunkt gespeichert“ ist, nicht „im Beendigungsereignis hart arbeiten“.

6. Wie sich ein Windows-Dienst verhalten sollte — SHUTDOWN und PRESHUTDOWN

6.1. Zwei Arten von Herunterfahren-Benachrichtigung

Ein Dienst ist von der Abmeldung nicht betroffen, wird aber beim Herunterfahren und Neustart gestoppt. Die Benachrichtigung kommt als Steuercode vom Service Control Manager (SCM), und sie zu empfangen erfordert, ein Annahme-Flag zu erklären.8

Erklärung Benachrichtigung, die ankommt Zeitpunkt und Schonfrist
SERVICE_ACCEPT_SHUTDOWN SERVICE_CONTROL_SHUTDOWN Während der Herunterfahren-Verarbeitung benachrichtigt. Standard etwa 20 Sekunden, Obergrenze WaitToKillServiceTimeout
SERVICE_ACCEPT_PRESHUTDOWN SERVICE_CONTROL_PRESHUTDOWN Vor SHUTDOWN benachrichtigt. Der SCM wartet, bis der Dienst stoppt oder das Timeout
Reihenfolge der Herunterfahren-Benachrichtigungen an einen DienstWenn das Herunterfahren beginnt, werden Dienste, die PRESHUTDOWN erklärt haben, zuerst mit der konfigurierten Schonfrist benachrichtigt, dann wird die SHUTDOWN-Benachrichtigung mit einem Standard von etwa 20 Sekunden gesendet, und der Prozess wird beendet, wenn die Schonfrist abläuftHerunterfahren beginntPRESHUTDOWN(falls erklärt)SHUTDOWN(etwa 20 s)Schonfrist abgelaufen → Beenden

Abbildung 9: Wenn das Herunterfahren beginnt, werden Dienste, die PRESHUTDOWN erklärt haben, zuerst mit der konfigurierten Schonfrist benachrichtigt, dann wird die SHUTDOWN-Benachrichtigung mit einem Standard von etwa 20 Sekunden gesendet, und der Prozess wird beendet, wenn die Schonfrist abläuft.

Das PRESHUTDOWN-Timeout lässt sich mit ChangeServiceConfig2 (SERVICE_CONFIG_PRESHUTDOWN_INFO) konfigurieren; der Standard ist 10 Sekunden ab Windows 10 Creators Update (Build 15063) und 3 Minuten davor.9 Wenn Sie noch mit dem alten Wissen arbeiten, dass „PRESHUTDOWN Ihnen 3 Minuten gibt“, haben Sie auf einem aktuellen Betriebssystem nur 1/18 der Schonfrist, die Sie erwartet haben. Außerdem hält PRESHUTDOWN das Herunterfahren des ganzen Systems für dieses Intervall auf, die Dokumentation sagt also ebenfalls, es „should be used only in special circumstances“.8

Praxis auf der Handler-Seite zählt ebenfalls. Der Steuerhandler muss innerhalb von 30 Sekunden zurückkehren; überlassen Sie zeitaufwendige Stopparbeit einem anderen Thread, melden Sie SERVICE_STOP_PENDING und kehren Sie sofort zurück.8

// Win32 service: accept PRESHUTDOWN and leave stop work to a worker
g_status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PRESHUTDOWN;

DWORD WINAPI ServiceHandlerEx(DWORD control, DWORD, LPVOID, LPVOID)
{
    switch (control)
    {
    case SERVICE_CONTROL_PRESHUTDOWN:
    case SERVICE_CONTROL_STOP:
        ReportStatus(SERVICE_STOP_PENDING, /*waitHintMs*/ 3000);
        SetEvent(g_stopEvent);   // Tell the worker to stop and return immediately
        return NO_ERROR;
    }
    return ERROR_CALL_NOT_IMPLEMENTED;
}

// Worker side: if cleanup runs longer than waitHint, keep reporting
// SERVICE_STOP_PENDING periodically while incrementing dwCheckPoint.
// The SCM judges "still alive and making progress" from waitHint and
// checkpoint advance. If reporting stops it can be treated as hung and
// shutdown can proceed. Always report SERVICE_STOPPED when finished

6.2. Ein Entwurf, der sich nicht auf die Schonfrist stützt

Die Obergrenze der Schonfrist, WaitToKillServiceTimeout, von der Dienstseite umzuschreiben, um sie zu verlängern, wird ausdrücklich nicht empfohlen. Die Dokumentation verlangt das Gegenteil — ein Dienst sollte Aufräumen so schnell wie möglich beenden, damit eine USV-gespeiste Maschine das Herunterfahren abschließen kann, bevor der Akku leer ist. Die Leitlinie ist, im gewöhnlichen Betrieb häufig zu speichern, sodass ungespeicherte Daten minimiert sind, beim Herunterfahren nicht Zeit mit dem Freigeben von Speicher zu verbringen und beim Benachrichtigen eines Netzwerkpartners nicht zu lange auf eine Antwort zu warten. Außerdem berücksichtigt der SCM beim Herunterfahren standardmäßig keine Abhängigkeiten, die Stoppverarbeitung muss also noch funktionieren, „selbst wenn ein Dienst, von dem Sie abhängen, bereits weg ist“.8

Stoppverarbeitung entwerfen, die sich nicht auf die Schonfrist stütztWenn Sie an jedem Verarbeitungsprüfpunkt speichern, sodass ungespeicherte Daten immer minimal sind, endet Aufräumen, wenn die Stoppbenachrichtigung ankommt, in wenigen Sekunden. Ein Entwurf, der alles beim Beenden speichert, passt nicht in die Schonfrist, und eine Zwangsbeendigung verliert die DatenAn jedem Prüfpunkt speichernStopp → Rest speichern → fertigAlles beim Beenden speichernStopp → Speichern verfehlt die SchonfristZwangskill → Datenverlust

Abbildung 10: Wenn Sie an jedem Verarbeitungsprüfpunkt speichern, sodass ungespeicherte Daten immer minimal sind, endet Aufräumen, wenn die Stoppbenachrichtigung ankommt, in wenigen Sekunden. Ein Entwurf, der alles beim Beenden speichert, passt nicht in die Schonfrist, und eine Zwangsbeendigung verliert die Daten.

In einem .NET Worker Service (UseWindowsService) werden SERVICE_CONTROL_STOP und SHUTDOWN in Host-Stopp übersetzt, und BackgroundService.StopAsync wird aufgerufen. Die Lagerimplementierung zum Zeitpunkt dieses Schreibens akzeptiert die Familie STOP/SHUTDOWN; wenn Sie PRESHUTDOWN ebenfalls brauchen, brauchen Sie einen erweiterten Handler. In jedem Fall machen Sie HostOptions.ShutdownTimeout ausdrücklich und beenden Sie StopAsync in wenigen Sekunden. Zum Bauen eines Dienstes im Allgemeinen siehe „Windows-Dienste erstellen und betreiben“.

7. Nach einem Neustart automatisch wiederherstellen

Auf einem Geräte-PC oder einem unbeaufsichtigten PC ist der Entwurfsumfang nicht nur „das Herunterfahren überstehen“, sondern „nach einem Neustart von selbst zurückkommen“.

7.1. RegisterApplicationRestart und der Wiederherstellungs-Callback

Wenn Sie RegisterApplicationRestart aufgerufen haben, wird die App als Neustartkandidat für Absturz (unbehandelte Ausnahme), „Keine Rückmeldung“, einen updategetriebenen App-Neustart und einen updategetriebenen Betriebssystem-Neustart registriert. Sie können Kommandozeilenargumente für den Neustart registrieren, wenn Sie also „welche Datei offen war“ und „welcher Wiederherstellungspunkt“ einschließen, können Sie nach dem Neustart dort weitermachen, wo Sie aufgehört haben.10

Die Spezifikationen, die Sie aufnehmen sollten, sind wie folgt.10

  • Die Registrierung muss beendet sein, bevor das Problem auftritt (während der WM_QUERYENDSESSION-Behandlung ist die letzte Chance in einem Update-Szenario)
  • Um eine Neustartschleife zu verhindern, wird ein Prozess, der weniger als 60 Sekunden gelaufen ist, nicht neu gestartet
  • Ein Prozess, der erhöht läuft, ist kein Kandidat für automatischen Neustart (der Prozess kann ohne Zustimmungsdialog zur Erhöhung nicht neu erzeugt werden). Automatische Wiederherstellung einer App, die Erhöhung braucht, wird entworfen, indem die UI auf Standardberechtigung bleibt und privilegierte Arbeit in einem Dienst isoliert wird, oder durch einen ausdrücklichen Startpfad wie eine Taskplaner-Aufgabe „Mit höchsten Privilegien ausführen“
  • Neustart nach einem Absturz oder Hänger geht durch die Zustimmung des Benutzers; Neustart nach einem Update ist automatisch
  • Um über einen Betriebssystem-Neustart hinweg wiederherzustellen, muss die Seite, die den Neustart anfordert (ein Installer und Ähnliches), die Herunterfahren-API mit den Flags EWX_RESTARTAPPS / SHUTDOWN_RESTARTAPPS aufrufen

Wenn Sie auch RegisterApplicationRecoveryCallback registrieren, ruft WER (Windows Error Reporting) den Callback bei einem Absturz auf und gibt Ihnen eine Schonfrist, um laufende Daten zu speichern. Wenn das Speichern Zeit braucht, müssen Sie allerdings ApplicationRecoveryInProgress innerhalb des bei der Registrierung angegebenen Ping-Intervalls weiter aufrufen, sonst wird die Wiederherstellungsarbeit mittendrin gekappt. Wenn das Speichern fertig ist, benachrichtigen Sie den Abschluss mit ApplicationRecoveryFinished. „Eine in Verwendung befindliche Datei ersetzen und neu starten“ zur App-Update-Zeit ist das Gebiet von Restart Manager, ausführlich in „Wie man eine exe oder DLL austauscht, die gerade verwendet wird“ behandelt.

7.2. ARSO — automatische Anmeldung nach einem Update-Neustart

Nach einem Windows-Update-Neustart kommen Benutzersitzungs-Apps nicht zurück, wenn sich niemand anmeldet. Was diese Lücke füllt, ist ARSO (Winlogon Automatic Restart Sign-On). Wenn Windows Update einen Neustart startet, speichert es die Anmeldeinformationen des letzten interaktiven Benutzers sicher, konfiguriert Autologon und meldet diesen Benutzer nach dem Neustart automatisch an und sperrt dann den Bildschirm.11 Es gibt auch einen Befehl wie shutdown /g, der einen Neustart plus Wiederaufnahme registrierter Apps anfordert. Einige Umgebungen deaktivieren das mit Organisationsrichtlinie (DisableAutomaticRestartSignOn und Ähnliches), wenn Sie also unbeaufsichtigte Wiederherstellung entwerfen, prüfen Sie diese Einstellung als Satz. Und wenn Sie sich für Hintergrundarbeit, die Sie immer brauchen, auf automatischen Start in einer Benutzersitzung verlassen, ist der richtige Zug, sie von vornherein zu einem Windows-Dienst zu machen.

Pfad, auf dem eine App nach einem Neustart automatisch wiederhergestellt wirdWenn Sie RegisterApplicationRestart registrieren, bevor ein Problem auftritt, wird die App nach Benutzerzustimmung bei Absturz oder „Keine Rückmeldung“ neu gestartet und nach ARSO-Automatikanmeldung und Bildschirmsperre bei einem updategetriebenen Neustart. Prozesse unter 60 Sekunden Laufzeit und erhöhte Prozesse liegen außerhalbZustimmungRegisterApplicationRestartAbsturz oder HängerUpdate-NeustartApp-NeustartARSO-Anmeldung + SperreNicht: unter 60 s / erhöht

Abbildung 11: Wenn Sie RegisterApplicationRestart registrieren, bevor ein Problem auftritt, wird die App nach Benutzerzustimmung bei Absturz oder „Keine Rückmeldung“ neu gestartet und nach ARSO-Automatikanmeldung und Bildschirmsperre bei einem updategetriebenen Neustart. Prozesse unter 60 Sekunden Laufzeit und erhöhte Prozesse liegen außerhalb.

8. Einen Stromverlust ohne Benachrichtigung überstehen — Schreibentwurf und USV

8.1. Ein Schreiben, das „nicht kaputtgeht, egal wann es gekappt wird“ — temporäre Datei + ReplaceFile

Ein ausgelöster Sicherungsautomat, ein ausgefallenes Netzteil oder ein gezogener Stecker bringt weder WM_ENDSESSION noch PRESHUTDOWN. Solange Sie die Originaldatei für Einstellungen oder Messergebnisse „an Ort und Stelle überschreiben“, kann ein Stromverlust mitten im Schreiben eine kaputte Datei hinterlassen, die Alt und Neu mischt.

Das Standardmuster ist, vollständig in eine temporäre Datei auf demselben Volume zu schreiben und dann zu tauschen. ReplaceFile packt die Folge „in die neue Datei speichern → das Original beiseitelegen → umbenennen → löschen“ in eine einzige API und übernimmt auch Attribute der Originaldatei wie Erstellungszeit, ACL und alternative Datenströme (die drei Dateien müssen auf demselben Volume liegen).12 .NETs File.Replace ruft das unverändert auf.

Speicher- und Wiederherstellungsablauf mit temporärer Datei und ReplaceFileBeim Speichern vollständig in eine temporäre Datei schreiben, leeren und mit ReplaceFile tauschen, wobei der alte Inhalt in .bak bleibt. Beim nächsten Start die Primärdatei prüfen und auf .bak zurückfallen, wenn sie kaputt istBeim nächsten StartBeim SpeichernUnversehrtKaputtStromverlust an jedem SchrittPrimärdatei prüfenUnverändert nutzenAuf .bak zurückfallenAuf den Datenträger leerenEine vollständige Temp-Datei schreibenReplaceFile → .bak

Abbildung 12: Beim Speichern vollständig in eine temporäre Datei schreiben, leeren und mit ReplaceFile tauschen, wobei der alte Inhalt in .bak bleibt. Beim nächsten Start die Primärdatei prüfen und auf .bak zurückfallen, wenn sie kaputt ist.

// The standard pattern for settings and data: write completely to a temporary file, then swap, and keep the old contents
public static void SaveAtomically(string path, string content)
{
    string dir = Path.GetDirectoryName(path)!;
    string tmp = Path.Combine(dir, Path.GetRandomFileName());  // Create it on the same volume

    try
    {
        using (var fs = new FileStream(tmp, FileMode.CreateNew, FileAccess.Write))
        using (var writer = new StreamWriter(fs))
        {
            writer.Write(content);
            writer.Flush();
            fs.Flush(flushToDisk: true);   // FlushFileBuffers equivalent. Write the OS buffer
                                           // out to disk (device-side cache limits are in 8.2)
        }

        if (File.Exists(path))
            File.Replace(tmp, path, path + ".bak");  // Calls ReplaceFile. Keep the old contents as .bak
        else
            File.Move(tmp, path);
    }
    catch
    {
        // If we fail mid-way, do not leave the temporary file. Repeated failures
        // of a periodic save would fill the volume with complete copies
        try { File.Delete(tmp); } catch { /* Prefer the original exception if delete fails */ }
        throw;
    }
}

Damit können Sie im gewöhnlichen Betrieb immer entweder „eine vollständige alte Datei“ oder „eine vollständige neue Datei“ lesen. ReplaceFile ist allerdings eine mehrstufige Namensraumoperation, und Atomizität über einen Stromverlust hinweg ist spezifikationsseitig nicht garantiert. Deshalb behält das Beispiel oben eine Sicherung (.bak) — die Leseseite prüft die Primärdatei beim Start und fällt auf die Sicherung zurück, wenn sie kaputt ist, als Satz. Für nur-anhängende Protokolle oder CSV können Sie das nicht nutzen, die nutzen also ein Format, das Bruch einbaut, etwa „eine Zeile = ein Datensatz, und eine kaputte letzte Zeile beim Lesen verwerfen“.

8.2. Erfolg von WriteFile ist nicht Ankunft auf dem Datenträger

Die andere Voraussetzung ist, dass selbst wenn WriteFile Erfolg zurückgibt, die Daten noch nur im Betriebssystemcache sein können. Windows legt Dateilesen und -schreiben auf den Systempuffer und spiegelt sie periodisch mit verzögertem Schreiben auf den Datenträger. Um Daten mit Sicherheit auf den Datenträger zu bekommen, leeren Sie entweder ausdrücklich mit FlushFileBuffers oder geben Sie FILE_FLAG_WRITE_THROUGH bei CreateFile an, sodass jedes Schreiben durch den Cache geht. Dateisystemmetadaten werden immer zwischengespeichert, das Bestätigen von Metadaten braucht also ebenfalls ein Leeren oder Write-Through.13

FlushFileBuffers jedes Mal aufzurufen ist allerdings ineffizient, und die Dokumentation ermutigt ebenfalls, FILE_FLAG_NO_BUFFERING+WRITE_THROUGH statt häufiger Aufrufe zu erwägen.13 In der Praxis ist „nur an einem Transaktionsprüfpunkt oder knapp vor dem Schließen der Datei leeren“ ein realistischer Kompromiss. Die Mechanik dieser Schicht — der Cache-Manager, verzögertes Schreiben und die Tatsache, dass „ich habe geleert und es hat den Datenträger trotzdem möglicherweise nicht erreicht“ wegen Hardware-Cache — ist ausführlich in „Cache-Manager: Wann erreicht Ihr WriteFile tatsächlich die Festplatte?“ behandelt.

8.3. USV und Akkuüberwachung — einen Stromverlust in ein Herunterfahren verwandeln

Die echte Gegenmaßnahme gegen Stromverlust auf einem Geräte-PC ist eine USV. Denken Sie die Rolle der USV nicht als „einen Stromausfall stoppen“, sondern als „einen Stromverlust ohne Benachrichtigung“ in „ein geplantes Herunterfahren mit einer Benachrichtigung“ zu verwandeln. Der Entwurf ist eine zweistufige Ausstattung.

  1. Die Schonfrist entwerfen: USV-Akku-Haltezeit > die Summe aus „Wechsel auf Akku erkennen → App- und Dienstaufräumen → Betriebssystem-Herunterfahren fertig“. Wenn die Dienststoppverarbeitung langsam ist, hält diese Gleichung nicht mehr (Abschnitt 6.2)
  2. Erkennung: Der Wechsel von Netz auf Akku und ein Abfall der Restkapazität werden mit dem Ereignis PBT_APMPOWERSTATUSCHANGE benachrichtigt. Eine App mit einem Fenster empfängt es als WM_POWERBROADCAST; ein Dienst ohne Fenster erklärt SERVICE_ACCEPT_POWEREVENT und empfängt es als SERVICE_CONTROL_POWEREVENT in HandlerEx (WM_POWERBROADCAST kommt nicht bei einem Dienststeuerhandler an). Beim Empfang rufen Sie GetSystemPowerStatus auf, prüfen ACLineStatus (ob am Netz) und BatteryLifePercent und führen in das Unterbrechen der Messung, Speichern und Anfordern des Herunterfahrens15
Ablauf, einen Stromverlust mit einer USV in ein geplantes Herunterfahren zu verwandelnWenn ein Ausfall die USV auf Akku umschaltet, wird PBT_APMPOWERSTATUSCHANGE benachrichtigt, der Energiezustand geprüft, und Speichern plus eine Herunterfahren-Anforderung verwandeln einen Stromverlust ohne Benachrichtigung in ein geplantes Herunterfahren mit einer BenachrichtigungAusfallUSV auf AkkuPBT_APMPOWERSTATUSCHANGEGetSystemPowerStatusUnterbrechen und speichernHerunterfahren anfordernGewöhnlicher Benachrichtigungsablauf(3–6)

Abbildung 13: Wenn ein Ausfall die USV auf Akku umschaltet, wird PBT_APMPOWERSTATUSCHANGE benachrichtigt, der Energiezustand geprüft, und Speichern plus eine Herunterfahren-Anforderung verwandeln einen Stromverlust ohne Benachrichtigung in ein geplantes Herunterfahren mit einer Benachrichtigung.

Eine typische USB-angeschlossene USV erscheint Windows als Akku, Sie können sie also mit dieser Standard-API erkennen. Wenn die Herstellerverwaltungssoftware eine Funktion „Betriebssystem bei N % Rest herunterfahren“ hat, prüfen Sie auch, dass die Schwelle zur Aufräumzeit Ihrer App passt. Fortsetzen aus Schlaf oder Ruhezustand und lang laufende Probleme sind eine getrennte Achse, behandelt in „Energiesparmodus, Ruhezustand, Modern Standby und lang laufende Anwendungen“.

9. Wie Sie prüfen — Herunterfahren sicher ausprobieren

Herunterfahren-Behandlung neigt dazu, „wir haben es geschrieben, aber nie unter produktionsähnlichen Bedingungen ausprobiert“ zu werden. Halten Sie eine Prozedur, um es sicher zu prüfen.

  • Probieren Sie es auf einer Testmaschine oder einer VM: Probieren Sie es nicht zuerst auf einem Produktions-Geräte-PC. In einer Testumgebung mit einem Hyper-V-Prüfpunkt (Momentaufnahme) wiederholen Sie Herunterfahren, Neu starten und einen erzwungenen Stromverlust (die VM ausschalten). Ein VM-„Power off“ reproduziert allerdings nur „das Gastbetriebssystem stoppt ohne Vorankündigung“; es reproduziert nicht das Verschwinden des flüchtigen Caches eines physischen Datenträgers oder steuererabhängigen Bruch. Wenn Sie es als Geräte-PC ausliefern, ist die Endprüfung ein echter Stromschnitt-Test auf produktionsgleichem Hardware
  • Eine schnelle Prüfung mit Abmelden: Der Pfad WM_QUERYENDSESSION → WM_ENDSESSION läuft auch bei der Abmeldung (der einzige Unterschied ist, dass das Bit ENDSESSION_LOGOFF in lParam gesetzt ist), Sie können also bequem das Verhalten des Aufräumcodes auf einer Entwicklungsmaschine bestätigen1
  • Probieren Sie vollständiges Herunterfahren und Hybrid getrennt: Probieren Sie shutdown /s /t 0 (vollständig), shutdown /s /hybrid /t 0 (Standardverhalten) und shutdown /r /t 0 (Neustart) jeweils2
  • Messen Sie, wie lange Aufräumen dauert: Schreiben Sie einen Zeitstempel ins Protokoll am Anfang und Ende der Aufräumfunktion und messen Sie, ob es in 5 Sekunden (oder die konfigurierte Schonfrist für einen Dienst) passt
Vorgänge zum Prüfen und was jeder bestätigen kannAbmelden ist eine bequeme Prüfung des Benachrichtigungspfads; Herunterfahren-Befehl vollständig, Hybrid und Neustart bestätigen den Produktionsbenachrichtigungspfad und die Schonfrist; VM-Ausschalten testet plötzliche-Stopp-Resilienz; ein physischer Stromschnitt-Test ist die Endprüfung einschließlich physischem SpeicherAbmeldenPfad QUERY → ENDSESSIONshutdown /s /hybrid /rProduktionspfad + SchonfristVM-AusschaltenPlötzlicher GaststoppPhysischer StromschnittSpeicher eingeschlossen(Ende)

Abbildung 14: Abmelden ist eine bequeme Prüfung des Benachrichtigungspfads; Herunterfahren-Befehl vollständig, Hybrid und Neustart bestätigen den Produktionsbenachrichtigungspfad und die Schonfrist; VM-Ausschalten testet plötzliche-Stopp-Resilienz; ein physischer Stromschnitt-Test ist die Endprüfung einschließlich physischem Speicher.

Für nachträgliche Isolation ist das Ereignisprotokoll (System) nützlich. Bei einem normalen Herunterfahren oder Neustart wird Ereignis-ID 1074 (welcher Prozess das Herunterfahren gestartet hat, für wen und aus welchem Grund) aufgezeichnet. Bei einem plötzlichen Stromverlust oder Absturz gibt es kein 1074, und beim nächsten Boot werden Ereignis-ID 41 (Kernel-Power) und 6008 (Das vorherige Systemherunterfahren war unerwartet) aufgezeichnet.14 „Was über Nacht passiert ist“ beginnt hier.

# Check the recent history of shutdown-related events
Get-WinEvent -FilterHashtable @{ LogName = 'System'; Id = 1074, 6008, 41 } -MaxEvents 20 |
    Select-Object TimeCreated, Id, ProviderName, Message |
    Format-List

Wenn 1074 „einen Neustart durch Windows Update“ zeigt und die Daten der App kaputt waren, ist das Problem der Aufräumcode. 6008/41 zeigen nur „ein unerwartetes Herunterfahren“; sie werden auch für einen Bluescreen (Absturz) oder einen erzwungenen Reset aufgezeichnet, nicht nur für einen Stromverlust. Wenn BugcheckCode von 41 ungleich null ist, ist es ein Absturz; wenn es 0 ist und es auch keinen Speicherabzug gibt, ist ein Stromverlust wahrscheinlich — isolieren Sie die Ursache aus den umgebenden Informationen, und sobald Sie wissen, dass es ein Stromverlust war, sind Kapitel 8s Schreibentwurf und eine USV der nächste Schritt.

10. Zusammenfassung

  • Herunterfahren ist „ein normales Ereignis, das früher oder später kommt“. Die Schonfrist nach der Benachrichtigung beträgt grundsätzlich nur etwa 5 Sekunden, die Voraussetzung ist also häufiges automatisches Speichern, sodass „was Sie beim Beenden tun“ minimiert ist.
  • Auf Client-Betriebssystemen ab Windows 8 ist „Herunterfahren“, wenn Schnellstart aktiviert ist, ein Hybrid-Herunterfahren und der Kernel geht nur in den Ruhezustand. Der einzige vollständige Reset ist „Neu starten“ — schreiben Sie „Neu starten“ in die Vorfallprozedur.
  • Eine GUI-App gibt TRUE sofort auf WM_QUERYENDSESSION zurück und macht festgelegtes Aufräumen in WM_ENDSESSION. WinForms/WPF FormClosing und SessionEnding entsprechen der Abfragephase, was Sie dort tun, ist also höchstens eine idempotente Schnappspeicherung. Stellen Sie während des Herunterfahrens keinen Dialog.
  • Eine Operation, die wirklich nicht unterbrochen werden kann, wird geschützt, indem eine Begründung mit ShutdownBlockReasonCreate angezeigt wird. Es gibt allerdings nirgendwo eine Garantie, dass Sie blockieren können.
  • Eine Konsolen-App empfängt die Benachrichtigung mit SetConsoleCtrlHandler; ein Dienst mit SERVICE_ACCEPT_PRESHUTDOWN/SHUTDOWN. Die Standard-PRESHUTDOWN-Schonfrist beträgt auf einem aktuellen Betriebssystem 10 Sekunden. In .NET hören Sie auf, sich auf ProcessExit zu verlassen, und wechseln zum kanonischen Pfad für das App-Modell.
  • Wiederherstellung nach einem Neustart kann unbeaufsichtigt mit RegisterApplicationRestart (+ einem Wiederherstellungs-Callback) und ARSO erfolgen.
  • Ein Stromverlust bringt keine Benachrichtigung. Bereiten Sie mit einem Tausch temporäre Datei + ReplaceFile (als Satz mit Sicherung + Prüfung zur Ladezeit), einem Leeren an Prüfpunkten und einer USV vor, die „einen Stromverlust in ein geplantes Herunterfahren verwandelt“.
Das Gesamtbild der Herunterfahren-BehandlungFür ein Ende mit Benachrichtigung mit Aufräumen antworten, das den Laden in wenigen Sekunden schließen kann, und in automatische Wiederherstellung nach einem Neustart führen; für einen Stromverlust ohne Benachrichtigung mit einem Schreiben vorbereiten, das nicht kaputtgeht, egal wann es gekappt wird, und einer USV, und einschließlich auf physischer Hardware prüfen. Diese zwei Säulen sind die Schlussfolgerung des ArtikelsMit BenachrichtigungOhne BenachrichtigungWie es endetAufräumen in wenigen Sekunden(3–6)Sicheres Schreiben + USV(8)Automatisch nach Neustart wiederherstellen(7)Auf Hardware prüfen(9)

Abbildung 15: Für ein Ende mit Benachrichtigung mit Aufräumen antworten, das den Laden in wenigen Sekunden schließen kann, und in automatische Wiederherstellung nach einem Neustart führen; für einen Stromverlust ohne Benachrichtigung mit einem Schreiben vorbereiten, das nicht kaputtgeht, egal wann es gekappt wird, und einer USV, und einschließlich auf physischer Hardware prüfen. Diese zwei Säulen sind die Schlussfolgerung des Artikels.

  • Prüfen Sie sicher auf einer VM und mit Abmelden, und isolieren Sie nachträglich mit Ereignis-IDs 1074/41/6008.

Das nächste Mal, wenn Sie der App eine Funktion hinzufügen, fragen Sie sich das einmal: wenn WM_ENDSESSION mitten in dieser Arbeit ankommt oder der Strom gezogen wird, was bleibt beim nächsten Start? Diese Antwort in den Entwurf zu schreiben, ist der kürzeste Weg, nie wieder einen Morgen vor einem Geräte-PC zu stehen und sich den Kopf zu halten.

Weiterführende Artikel

Zugehörige Beratungsfelder

KomuraSoft LLC übernimmt Entwurf und Implementierung von Gegenmaßnahmen gegen Herunterfahren und Stromverlust für Geräte-PC- und lang laufende Apps, Ursachenuntersuchung von Datenbeschädigung und „am Morgen war es stehengeblieben“-Vorfällen, die von einem Windows-Update-Neustart oder einer Abmeldung ausgehen, sowie Entwurfsreviews der Stoppverarbeitung von Windows-Diensten und der automatischen Wiederherstellung. Es ist in Ordnung, vom Stadium „irgendetwas scheint bei jedem Herunterfahren kaputtzugehen, und ich weiß nicht, wo ich anfangen soll“ zu starten.

Quellen

  1. Microsoft Learn, WM_QUERYENDSESSION message. Dazu, dass WM_QUERYENDSESSION beim Sitzungsende gesendet wird und die App TRUE zurückgeben und die Absicht des Benutzers respektieren sollte (der Standard von DefWindowProc ist ebenfalls TRUE); dass Aufräumen bis WM_ENDSESSION aufgeschoben werden sollte; dass das System nach 5 Sekunden UI für Apps anzeigt, die das Herunterfahren verhindern, und der Benutzer zwangsbeenden kann; die Bedeutung der Bits ENDSESSION_LOGOFF/CLOSEAPP/CRITICAL in lParam; dass Herunterfahren und Neustart nicht unterschieden werden können; und dass Daten häufig gespeichert werden sollten, sodass weniger beim Beenden gespeichert werden muss.  2 3 4 5 6 7

  2. Microsoft Learn, Fast startup causes hibernation or shutdown to fail in Windows 10 or Windows 8.1. Dazu, dass mit Schnellstart die Kernelsitzung nicht geschlossen und als Ruhezustand behandelt wird und Kernel- und Gerätetreiberzustand in hiberfil.sys gespeichert werden; dass „Neu starten“ immer einen vollständigen Boot macht, weil ein vollständig neuer Windows-Zustand nötig ist; dass Schnellstart standardmäßig aktiviert ist und das Deaktivieren nicht empfohlen wird; und dass der Standard von Shutdown.exe ein vollständiges Herunterfahren ist, wobei die Option /hybrid Hybridverhalten erzeugt.  2 3 4 5

  3. Microsoft Learn, ShutdownBlockReasonCreate function (winuser.h). Dazu, dass Sie sie am Anfang einer ununterbrechbaren Operation aufrufen, um eine Begründungszeichenfolge zu registrieren, und ShutdownBlockReasonDestroy aufrufen, wenn fertig; dass sie nur vom Thread aufgerufen werden kann, der das Fenster erzeugt hat; und dass der Benutzer die Begründung nur wenige Sekunden liest, die Zeichenfolge also kurz und klar sein sollte.  2 3

  4. Microsoft Learn, Shutdown Changes for Windows Vista. Dazu, dass die Antwort auf WM_QUERYENDSESSION/WM_ENDSESSION jeweils um 5 Sekunden verzögert werden kann und der Benutzer dann Fortsetzen oder Abbrechen wählen kann; dass eine Konsolen-App oder eine App ohne sichtbares Fenster das Herunterfahren nicht abbrechen kann und nach 5 Sekunden ohne Antwort oder einer FALSE-Antwort automatisch beendet wird; dass bei Bedarf eines Blocks eine Begründung mit ShutdownBlockReasonCreate registriert werden sollte; und dass eine App sich nicht darauf verlassen darf, das Herunterfahren blockieren zu können.  2 3 4

  5. Microsoft Learn, HandlerRoutine callback function. Die von einem mit SetConsoleCtrlHandler registrierten Handler empfangenen Ereignisse CTRL_C/BREAK/CLOSE/LOGOFF/SHUTDOWN; dass das Standardtimeout für CTRL_CLOSE_EVENT etwa 5000 Millisekunden und für CTRL_SHUTDOWN_EVENT auf einem Dienstprozess etwa 20000 Millisekunden beträgt; dass CTRL_LOGOFF/SHUTDOWN_EVENT im Wesentlichen nur von Diensten empfangen werden, weil eine interaktive App bei der Abmeldung beendet wird; und dass der Handler auf einem getrennten Thread läuft.  2 3

  6. Microsoft Learn, SetConsoleCtrlHandler function. Dazu, dass ein Prozess, der gdi32.dll oder user32.dll geladen hat, als Windows-App behandelt wird und die Handler CTRL_LOGOFF_EVENT/CTRL_SHUTDOWN_EVENT nicht aufgerufen werden; dass die Umgehung ist, ein verstecktes Fenster zu erzeugen und WM_QUERYENDSESSION/WM_ENDSESSION zu behandeln; und dass Konsolenfunktionen während der Signalbehandlung möglicherweise nicht korrekt funktionieren.  2

  7. Microsoft Learn, .NET runtime no longer provides default termination signal handlers. Dazu, dass die Laufzeit ab .NET 10 keinen Standardhandler mehr für Windows CTRL_SHUTDOWN_EVENT/CTRL_CLOSE_EVENT (die Unix-Äquivalente SIGTERM/SIGHUP) bereitstellt; dass die Standardbehandlung des Betriebssystems die App sofort beendet und AppDomain.ProcessExit und AssemblyLoadContext.Unloading nicht mehr feuern; und dass zum App-Modell passende Signalbehandlung in einer höherstufigen Bibliothek oder im App-Code registriert werden sollte.  2

  8. Microsoft Learn, Service Control Handler Function. Dazu, dass ein Dienst, der SERVICE_ACCEPT_PRESHUTDOWN erklärt hat, zuerst SERVICE_CONTROL_PRESHUTDOWN empfängt, dann ein SERVICE_ACCEPT_SHUTDOWN-Dienst SERVICE_CONTROL_SHUTDOWN; dass die Standardschonfrist beim Herunterfahren etwa 20 Sekunden beträgt und die Obergrenze beim Betriebssystem-Neustart WaitToKillServiceTimeout ist; dass dieser Wert nicht verlängert werden sollte; dass der Steuerhandler innerhalb von 30 Sekunden zurückkehren, STOP_PENDING und einen Wartehinweis melden und lange Arbeit einem anderen Thread überlassen sollte; dass Aufräumen so schnell wie möglich mit Blick auf USV-Betrieb fertig sein sollte; und dass der SCM beim Herunterfahren standardmäßig keine Abhängigkeiten berücksichtigt.  2 3 4 5

  9. Microsoft Learn, SERVICE_PRESHUTDOWN_INFO structure (winsvc.h). Dazu, dass der SCM nach der PRESHUTDOWN-Benachrichtigung wartet, bis der Dienst stoppt oder das Timeout; dass das Standardtimeout 10 Sekunden ab Windows 10 Creators Update (Build 15063) und 3 Minuten davor beträgt; dass es mit ChangeServiceConfig2 konfiguriert wird; und dass der Status während SERVICE_STOP_PENDING weiter aktualisiert werden kann.  2

  10. Microsoft Learn, RegisterApplicationRestart function (winbase.h). Dazu, dass Neustart für Absturz, „Keine Rückmeldung“, Update und einen Computerneustart, der ein Update begleitet, registriert werden kann; dass Kommandozeilenargumente für den Neustart angegeben werden können; dass die Registrierung vor dem Auftreten des Problems erfolgen muss und die WM_QUERYENDSESSION-Behandlung die letzte Chance in einem Update-Szenario ist; dass ein Prozess unter 60 Sekunden Laufzeit nicht neu gestartet wird; dass Neustart nach Absturz oder Hänger durch die Zustimmung des Benutzers geht; und dass das Überschreiten eines Betriebssystem-Neustarts ein Herunterfahren mit EWX_RESTARTAPPS/SHUTDOWN_RESTARTAPPS erfordert.  2 3

  11. Microsoft Learn, Winlogon automatic restart sign-on (ARSO). Dazu, dass Windows Update bei einem automatischen Neustart die Anmeldeinformationen des letzten interaktiven Benutzers speichert und Autologon konfiguriert; dass es den Benutzer nach dem Neustart automatisch anmeldet und die Sitzung sperrt; dass die gespeicherten Anmeldeinformationen nach einer erfolgreichen Anmeldung gelöscht werden; und dass es mit Gruppenrichtlinie (DisableAutomaticRestartSignOn und Ähnliches) konfiguriert werden kann.  2

  12. Microsoft Learn, ReplaceFileW function (winbase.h). Dazu, dass ReplaceFile die mehreren Schritte, die „in die neue Datei speichern, das Original vorübergehend umbenennen, die neue Datei umbenennen, das Original löschen“ entsprechen, in eine einzige Funktion packt; dass es Attribute der Originaldatei wie Erstellungszeit, DACL, Verschlüsselung, Komprimierung und benannte Datenströme erhält; und dass die Sicherung, die zu ersetzende Datei und die Ersatzdatei auf demselben Volume liegen müssen.  2

  13. Microsoft Learn, File Caching. Dazu, dass Schreiben standardmäßig auf den Systemcache gehen und durch verzögertes Schreiben auf den Datenträger gespiegelt werden; dass FILE_FLAG_WRITE_THROUGH sofort auf den Datenträger schreibt; dass FlushFileBuffers ausdrücklich leeren kann; und dass Dateisystemmetadaten immer zwischengespeichert werden, das Bestätigen von Metadaten also ein Leeren oder Write-Through erfordert.  2 3

  14. Microsoft Learn, Troubleshoot unexpected reboots using system event logs. Dazu, dass ein normaler Neustart Ereignis-ID 1074 aufzeichnet (welcher Prozess das Herunterfahren gestartet hat, für wen und aus welchem Grund); dass ein unerwarteter Neustart Ereignis-ID 41 (Kernel-Power) und 6008 (das vorherige Herunterfahren war unerwartet) aufzeichnet; und dass diese IDs die Art des Neustarts isolieren können.  2

  15. Microsoft Learn, PBT_APMPOWERSTATUSCHANGE event. Dazu, dass dieses Ereignis über WM_POWERBROADCAST bei einem Wechsel zwischen Akku und Netz oder einem Abfall der Restkapazität benachrichtigt wird; und dass Sie beim Empfang GetSystemPowerStatus aufrufen und SYSTEM_POWER_STATUS-Felder wie ACLineStatus, BatteryFlag und BatteryLifePercent prüfen sollten. 

Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.

Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.

Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.

Häufige Fragen

Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.

Ein Problem, das nach „Herunterfahren“ nicht wegging, ging nach einem „Neu starten“ weg. Warum?
Auf Client-Betriebssystemen ab Windows 8 nutzt „Herunterfahren“, wenn Schnellstart aktiviert ist (der Standard auf den meisten PCs, die Ruhezustand unterstützen), einen Mechanismus namens Hybrid-Herunterfahren. Der Benutzer wird abgemeldet, aber Kernel- und Treiberzustand werden in die Ruhezustandsdatei gespeichert und beim nächsten Boot unverändert wiederhergestellt. Mit anderen Worten: Der Kern des Betriebssystems wurde nicht zurückgesetzt. „Neu starten“ führt dagegen immer einen vollständigen Boot aus, Treiber- und Dienstprobleme werden also zurückgesetzt. Schreiben Sie „Neu starten“ in Ihre Isolationsprozedur, nicht „Herunterfahren und wieder einschalten“. Wenn Sie ein vollständiges Herunterfahren von der Kommandozeile wollen, können Sie shutdown /s nutzen.
Kann ich das Herunterfahren anhalten, bis die App mit dem Speichern fertig ist?
Sie können vorübergehend um Warten bitten, aber Sie können es nicht zuverlässig anhalten. Wenn Sie eine Begründungszeichenfolge mit ShutdownBlockReasonCreate nur registrieren, solange eine ununterbrechbare Operation läuft, erscheint diese Begründung auf dem Bildschirm „Diese App verhindert das Herunterfahren“, und der Benutzer kann entscheiden, ob fortgesetzt oder abgebrochen wird. Der Benutzer kann trotzdem die Fortsetzung erzwingen, und ein erzwungenes Herunterfahren oder ein updategetriebener Neustart wartet möglicherweise gar nicht. Der richtige Ansatz ist daher nicht „blockieren“, sondern häufiges automatisches Speichern, sodass weniger Daten gefährdet sind, plus Aufräumen, das so entworfen ist, dass es in wenigen Sekunden ab der Beendigungsbenachrichtigung fertig ist.
Das Stoppen meines Windows-Dienstes dauert lange. Kann ich die Schonfrist beim Herunterfahren verlängern?
In der Standardkonfiguration, die SERVICE_CONTROL_SHUTDOWN empfängt, beträgt die Schonfrist etwa 20 Sekunden und hängt vom Registrierungswert WaitToKillServiceTimeout ab. Diesen Wert von der App-Seite umzuschreiben, um ihn zu verlängern, wird nicht empfohlen. Wenn Sie eine längere Schonfrist brauchen, können Sie SERVICE_ACCEPT_PRESHUTDOWN erklären und SERVICE_CONTROL_PRESHUTDOWN empfangen; Sie werden früher als andere benachrichtigt, und das Timeout lässt sich mit ChangeServiceConfig2 konfigurieren (der Standard ist 10 Sekunden ab Windows 10 Creators Update und 3 Minuten davor). PRESHUTDOWN hält allerdings das gesamte Herunterfahren für dieses Intervall auf, beschränken Sie es also auf Fälle, die Sie wirklich brauchen, und entwerfen Sie die Stopparbeit selbst so, dass sie in wenigen Sekunden fertig ist.
Ist es sicher, Aufräumen beim Herunterfahren in .NETs AppDomain.ProcessExit zu machen?
Ich empfehle, sich nicht darauf zu verlassen. Historisch registrierte die Laufzeit einen Standard-Signalhandler, und ProcessExit feuerte bei CTRL_CLOSE_EVENT und CTRL_SHUTDOWN_EVENT, aber ab .NET 10 stellt die Laufzeit keine Standard-Beendigungssignalhandler mehr bereit, und ProcessExit feuert in diesen Fällen nicht mehr. Implementieren Sie Aufräumen auf dem Benachrichtigungspfad, der zum App-Modell passt: GUI-Apps nutzen FormClosing oder SessionEnding (das sind Abfragephasen-Benachrichtigungen, beschränken Sie sie also auf idempotente Speicherungen; Aufräumen, das nur nach dem Commit der Sitzung laufen kann, gehört in einen WM_ENDSESSION-Hook); Generic Host / Worker Service nutzen IHostApplicationLifetime und StopAsync; Konsolen-Apps nutzen SetConsoleCtrlHandler oder PosixSignalRegistration.
Wie verhindere ich, dass Dateien durch einen plötzlichen Stromverlust beschädigt werden?
Stromverlust bringt überhaupt keine Benachrichtigung, die einzige Option ist also, so zu schreiben, dass es nicht kaputtgeht, egal wann der Strom gekappt wird. Die Basis ist, die Originaldatei nicht an Ort und Stelle zu überschreiben: vollständig in eine temporäre Datei auf demselben Volume schreiben, leeren und mit ReplaceFile tauschen (File.Replace in .NET). Im gewöhnlichen Betrieb lässt Sie das entweder eine vollständige alte Datei oder eine vollständige neue Datei lesen, aber die Atomizität von ReplaceFile über einen Stromverlust hinweg ist spezifikationsseitig nicht garantiert, behalten Sie also eine Sicherung (das dritte Argument) und implementieren Sie Wiederherstellung zur Ladezeit, die die Primärdatei prüft und auf die Sicherung zurückfällt, wenn sie kaputt ist. Außerdem bedeutet Erfolg von WriteFile nicht, dass die Daten den Datenträger erreicht haben, bestätigen Sie also an wichtigen Prüfpunkten das Schreiben mit FlushFileBuffers oder FILE_FLAG_WRITE_THROUGH. Auf Geräte-PCs ist die Standardausstattung, das mit einer USV zu kombinieren, den Wechsel auf Akku zu erkennen und in ein sicheres Herunterfahren zu führen.

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.

Zurück zum Blog