Windows-Herunterfahren aus Sicht der App — Beendigungsbenachrichtigungen, Neustarts und Stromausfall richtig überstehen
· Aktualisiert am: · Go Komura · Windows, Herunterfahren, Windows-Entwicklung, Windows-Dienste, Geräte-PC, Datenintegrität, Dauerbetrieb, UPS
Änderungsverlauf (Erstfassung, veröffentlicht am 21. Aug 2026)
- Erstveröffentlichung
Diesen Artikel zitieren(DOI (registriertes Archiv): 10.5281/zenodo.22176519)
Die folgenden DOIs verweisen auf bereits archivierte Versionen, die vom aktuellen Text abweichen können. Verwenden Sie die URL dieser Seite, um auf den aktuellen Text zu verweisen.
Go Komura (2026). Windows-Herunterfahren aus Sicht der App — Beendigungsbenachrichtigungen, Neustarts und Stromausfall richtig überstehen. KomuraSoft LLC. https://comcomponent.com/de/blog/windows-shutdown-handling-for-apps/
- DOI (registriertes Archiv)
- 10.5281/zenodo.22176519
- DOI (zuletzt registrierte Version)
- 10.5281/zenodo.22176520
„Ein nächtlicher Windows-Update-Neustart hat die Datei beschädigt, in die wir gerade gemessen haben.“ „Nach dem Abmelden von einem gemeinsam genutzten PC ist der Inhalt verschwunden, den ich gerade bearbeitet habe.“ Um solche Unfälle zu verhindern, darf Herunterfahren nicht als Ausnahme gelten: Von außen zum Beenden aufgefordert zu werden, muss als normales Verhalten der App entworfen sein.
Code, der die Beendigungsbenachrichtigung empfängt, reicht allein nicht. Die Zeit nach der Benachrichtigung ist kurz, und ein plötzlicher Stromausfall bringt überhaupt keine Benachrichtigung. Die Achse dieses Artikels ist, alltäglichen Speichern, kurzes Aufräumen und Wiederherstellung beim nächsten Start zu einem durchgehenden Ganzen zu machen.
Für IT-Verantwortliche in kleinen und mittleren Unternehmen und für Entwickler von Windows-Apps, besonders von Geräte-PCs und lang laufenden Apps, ordnet dieser Artikel die Wahl des Benachrichtigungspfads, die Implementierung, die Wiederaufnahme nach einem Neustart, die Vorsorge für Stromausfall und die Prüfung. Die technische Grundlage sind die Microsoft-Learn-Primärquellen Stand August 2026, auf die der Originalartikel verweist.
1. Zuerst das Fazit — bevor Sie auf eine Benachrichtigung warten, verringern Sie, was gespeichert werden muss
Halten Sie die App in einem Zustand, in dem sie innerhalb weniger Sekunden nach einer Benachrichtigung beenden kann, und stellen Sie sicher, dass der nächste Start auch dann wiederherstellen kann, wenn keine Benachrichtigung kommt. Das ist die Grundlinie für den Umgang mit dem Herunterfahren.
Statt nach der Beendigungsbenachrichtigung große Datenmengen zu speichern, speichern Sie an jedem Meilenstein der Arbeit und halten Sie das restliche Delta beim Beenden klein. Für GUI-Apps und das Schließen der Konsole sind etwa 5 Sekunden die entscheidende Größe. Dienste haben eine andere Schonfrist, aber keine davon ist Zeit, die Sie garantiert vollständig bekommen.123
1.1. Den Benachrichtigungspfad Ihrer App wählen
| Form der App | Wo die Beendigungsanforderung ankommt | Zuerst richtig machen | Zu lesendes Kapitel |
|---|---|---|---|
| Win32-GUI-App | WM_QUERYENDSESSION und WM_ENDSESSION |
Auf die Abfrage grundsätzlich sofort TRUE zurückgeben. Das Aufräumen nach dem Commit der Beendigung in WM_ENDSESSION erledigen |
Kapitel 3 |
| WinForms / WPF | FormClosing / SessionEnding, bei Bedarf plus Nachrichten-Hook |
Beide Ereignisse gehören zur Abfragephase. Aufräumen, das sich nicht rückgängig machen lässt, in die Commit-Benachrichtigung verschieben | Kapitel 3 |
| Reine Konsolen-App | SetConsoleCtrlHandler und Ähnliches |
Schließen und Abmelden/Herunterfahren werden unter unterschiedlichen Bedingungen benachrichtigt | Kapitel 5 |
| Windows-Dienst | SHUTDOWN / PRESHUTDOWN vom SCM |
Das Annahme-Flag erklären und aus dem Steuerhandler sofort zurückkehren | Kapitel 6 |
| Generic Host / Worker Service | IHostApplicationLifetime und StopAsync |
Das Aufräumen auf dem Stoppfad des Hosts bündeln und ShutdownTimeout ausdrücklich setzen |
Kapitel 5 und 6 |
Verlassen Sie sich in .NET nicht allein auf AppDomain.ProcessExit. Ab .NET 10 stellt die Laufzeit keinen Standardhandler für CTRL_CLOSE_EVENT / CTRL_SHUTDOWN_EVENT bereit. Dieser Pfad über ein externes Signal ist getrennt von einem normalen Beenden wie der Rückkehr aus Main. Abschnitt 5.2 behandelt die Einzelheiten.4
1.2. Außerhalb der Benachrichtigungsbehandlung sind zwei Vorbereitungen nötig
Wenn die Benachrichtigung kommt, fragen Sie den Benutzer nicht „Möchten Sie speichern?“, sondern beenden Sie mit kurzem Aufräumen. Vorübergehendes Blockieren für Arbeit, die wirklich nicht unterbrochen werden kann, behandelt Kapitel 4, die automatische Wiederaufnahme nach einem Neustart Kapitel 7.
Die andere Vorbereitung ist, Daten zu hinterlassen, die auch nach einem Stromausfall ohne Benachrichtigung noch lesbar sind. Speichern in eine temporäre Datei, Leeren, Tauschen, Sichern und Prüfen beim Start fasst Kapitel 8 zusammen. Sobald der Benachrichtigungspfad implementiert ist, prüfen Sie den Speicher- und Wiederherstellungsentwurf in Kapitel 8 und die Prüfung in Kapitel 9.
flowchart TB
accTitle: Ein Speicherentwurf, der Beendigungsbenachrichtigung und Stromausfall gemeinsam ist
accDescr: Alltägliches Speichern hält das ungespeicherte Delta klein; mit Benachrichtigung folgt kurzes Aufräumen, ohne sie werden die gespeicherten Daten geprüft und wiederhergestellt, bevor die Arbeit weitergeht
daily["An jedem Meilenstein speichern"] --> event{"Gibt es beim Beenden eine Benachrichtigung?"}
event -->|"Ja"| close["Nur das restliche Delta speichern und beenden"]
event -->|"Nein"| lost["Vor Ort ist kein Aufräumen möglich"]
close --> boot["Beim nächsten Start prüfen und wiederherstellen"]
lost --> boot
boot --> resume["Arbeit wieder aufnehmen"]
Abbildung 1: Statt nur bei der Benachrichtigung hart zu arbeiten, machen Sie alltägliches Speichern bis zum nächsten Start zu einem durchgehenden Vorgang.
In der Abbildung kennzeichnet eine durchgezogene Linie eine stets geltende Beziehung und eine gestrichelte Linie eine bedingte (die Bedingungen stehen bei jeder Beziehung auf der Detailseite). Die vollständige Liste der Beziehungen (14 insgesamt, mit Beleg und Sicherheitsgrad) und die Definitionen der wichtigsten Konzepte sind auf der Detailseite der Wissenskarte (auf Japanisch) zusammengestellt. Daten: JSON-LD / Turtle
2. Zuerst unterscheiden — Abmelden, Herunterfahren, Neustart und Stromausfall
2.1. Benutzersitzung und Kernel getrennt betrachten
Wenn Sie „was endet“ in zwei Teile teilen, lässt sich die Behandlung ordnen. Die Benutzersitzung ist der Bereich, in dem die Apps dieses Benutzers laufen. Kernel und Treiber gehören dagegen zur Betriebssystemseite und laufen nach dem Abmelden weiter.
| Vorgang | Benutzersitzung | Kernel und Treiber | Benachrichtigungen |
|---|---|---|---|
| Abmelden | Endet | Läuft weiter | Die GUI erhält die Beendigungsabfrage und die Commit-Benachrichtigung. Dienste stoppen beim Abmelden nicht |
| Herunterfahren mit aktiviertem Schnellstart | Endet | Speichert den Ruhezustand in hiberfil.sys |
Die Sitzungsende-Benachrichtigungen der GUI und die Herunterfahrbenachrichtigung an Dienste |
| Neustart | Endet | Endet vollständig und startet das nächste Mal vollständig | Wie oben |
| Plötzlicher Stromausfall | Sofort verloren | Sofort verloren | Keine Benachrichtigung |
In WM_QUERYENDSESSION der GUI zeigt das Bit ENDSESSION_LOGOFF in lParam eine Abmeldung an. Ist lParam 0, handelt es sich um Herunterfahren oder Neustart, und die beiden sind nicht zu unterscheiden. Behandeln Sie lParam als Bitmaske.1
Für ungespeicherte Daten der App verlangen Abmelden und Herunterfahren dieselbe Vorsorge. Teilen Sie sie nicht in „beim Abmelden muss nicht gespeichert werden“; führen Sie beide in eine gemeinsame Speicherroutine. Das macht die Benachrichtigungsbedingungen von Konsole und Diensten nicht identisch; prüfen Sie die Pfade in den Kapiteln 5 und 6 jeweils gesondert.
flowchart TB
accTitle: Abmelden und Systemende getrennt denken
accDescr: Abmelden beendet die Apps des Benutzers, während Dienste weiterlaufen; ein Herunterfahren oder Neustart des Systems stoppt auch Dienste; ein Stromausfall benachrichtigt keines von beiden
signout["Abmelden"] --> user["Die Apps des Benutzers beenden"]
signout -.-> alive["Dienste laufen weiter"]
system["Herunterfahren oder Neustart"] --> user
system --> svc["Dienste werden ebenfalls gestoppt"]
power["Stromausfall"] --> none["Keine Benachrichtigung an beide"]
Abbildung 2: Eine Abmeldung übt die Beendigungsbehandlung der GUI, zählt aber nicht als Prüfung der Stoppbehandlung eines Dienstes.
2.2. Warum „Herunterfahren behebt es nicht, Neustart schon“
Auf Client-Betriebssystemen ab Windows 8 ist Schnellstart auf vielen PCs, die den Ruhezustand unterstützen, standardmäßig aktiviert. In dieser Konfiguration meldet ein Herunterfahren den Benutzer ab, aber der Zustand von Kernel und Gerätetreibern wird in die Ruhezustandsdatei geschrieben und beim nächsten Start wiederhergestellt. Den Strom zu kappen bedeutet nicht zwangsläufig, dass der gesamte OS-Zustand zurückgesetzt wurde.5
Dieses Verhalten ist bedingt. Wo der Ruhezustand deaktiviert ist (powercfg /hibernate off), wo Schnellstart per Richtlinie oder in den Energieoptionen ausgeschaltet ist, und auf Windows Server erhalten Sie das herkömmliche vollständige Herunterfahren. Prüfen Sie die Energieoptionen und mit powercfg /a, ob Schnellstart verfügbar ist.
„Neu starten“ führt dagegen immer einen vollständigen Startzyklus aus. Schreiben Sie in eine Prozedur zur Isolation von Treiberstörungen ausdrücklich „neu starten“, nicht „aus- und wieder einschalten“.5
flowchart TB
accTitle: Schnellstart gegenüber einem vollständigen Start
accDescr: Ein Herunterfahren mit aktiviertem Schnellstart speichert und stellt Kernel- und Treiberzustand wieder her, während ein vollständiges Herunterfahren oder ein Neustart alles mit einem vollständigen Start initialisiert
s["Herunterfahren"] --> q{"Schnellstart nutzen?"}
q -->|"Ja"| save["Kernel und Treiber in den Ruhezustand"]
save --> restore["Zustand beim nächsten Start wiederherstellen"]
q -->|"Nein"| full["Vollständiges Herunterfahren"]
full --> boot["Beim nächsten Mal mit vollständigem Start initialisieren"]
r["Neustart"] --> boot
Abbildung 3: Den Strom zu kappen ist nicht dasselbe, wie Kernel und Treiber zurückzusetzen.
Von der Kommandozeile fordert shutdown /s ein ausdrückliches vollständiges Herunterfahren und shutdown /s /hybrid das Hybridverhalten. Shutdown.exe ist standardmäßig ein vollständiges Herunterfahren; wichtig ist, nicht anzunehmen, das sei dasselbe wie der Eintrag „Herunterfahren“ auf dem Bildschirm.5
Schnellstart als Workaround zu deaktivieren, wird nicht empfohlen. Bauen Sie die App so, dass sie mit aktiviertem und mit deaktiviertem Schnellstart funktioniert. Schätzen Sie zum Beispiel die „kumulierte Betriebszeit“ eines Geräts nicht allein aus der OS-Startzeit; rechnen Sie im Entwurf damit, dass Kernelzustand mitgeführt werden kann.
3. GUI-Apps — Abfrage und committedes Beenden trennen
3.1. WM_QUERYENDSESSION ist die Frage, WM_ENDSESSION das Ergebnis
Eine App mit Fenster und Nachrichtenwarteschlange wird über das Sitzungsende in zwei Stufen benachrichtigt.1
| Nachricht | Bedeutung | Was zu tun ist |
|---|---|---|
WM_QUERYENDSESSION |
Die Abfrage „Darf beendet werden?“ | Grundsätzlich sofort TRUE zurückgeben. Auch DefWindowProc gibt standardmäßig TRUE zurück |
WM_ENDSESSION, wParam=TRUE |
Das Sitzungsende ist committed | Kurzes Aufräumen: speichern, trennen und so weiter |
WM_ENDSESSION, wParam=FALSE |
Das Sitzungsende wurde abgebrochen | Die App läuft weiter. Kein Aufräumen, das erst nach dem Commit der Beendigung möglich ist |
Selbst TRUE auf die Abfrage zurückzugeben, bedeutet nicht, dass die Beendigung feststeht. Eine andere App kann ablehnen, und die Beendigung wird abgebrochen. Wenn Sie an dieser Stelle trennen oder benötigte Ressourcen abgeben, kann eine App, die nicht beendet wurde, nicht mehr arbeiten. Deshalb wandert das Aufräumen in die Commit-Benachrichtigung.1
flowchart TB
accTitle: Abfrage der GUI und Ergebnis der Beendigung
accDescr: Selbst nach TRUE auf WM_QUERYENDSESSION kann eine andere App die Beendigung abbrechen, deshalb läuft Aufräumen nach dem Commit nur, wenn wParam von WM_ENDSESSION TRUE ist
query["WM_QUERYENDSESSION"] --> reply["Grundsätzlich sofort TRUE zurückgeben"]
reply --> result["WM_ENDSESSION"]
result --> yes{"Ist wParam TRUE?"}
yes -->|"Ja"| cleanup["Aufräumen nach dem Commit"]
yes -->|"Nein"| running["Beendigung abgebrochen; weiterlaufen"]
Abbildung 4: Verwechseln Sie die Antwort, die die Beendigung erlaubt, nicht mit der Benachrichtigung, dass die Beendigung committed ist.
Es gibt Fälle, in denen Sie FALSE zurückgeben und die Beendigung ablehnen können, aber die Regel ist, die Beendigungsabsicht des Benutzers zu respektieren. Eine App, die ablehnt, wird als App angezeigt, die das Herunterfahren verhindert. Konsolen-Apps und Apps ohne sichtbares Fenster haben ebenfalls Einschränkungen: In einer gewöhnlichen Konfiguration kann eine App, die nicht innerhalb von 5 Sekunden antwortet, automatisch beendet werden. Behandeln Sie Blockieren als die Ausnahmebehandlung von Kapitel 4 und nutzen Sie es nicht für gewöhnliches Speichern.6
3.2. Etwa 5 Sekunden sind keine Garantie, dass Sie zu Ende speichern können
Wenn Sie die Antwort in der Stufe WM_QUERYENDSESSION oder WM_ENDSESSION um etwa 5 Sekunden verzögern, zeigt das System den Bildschirm der Apps, die das Herunterfahren verhindern, und der Benutzer kann die Fortsetzung erzwingen. Nach einem erzwungenen Beenden gibt es keine Gelegenheit, den Rest des Speicherns zu beenden.6
Die Gegenmaßnahme ist, alltäglich zu speichern und das Delta beim Beenden zu verringern. Legen Sie ungespeicherten Arbeitszustand an einem temporären Ort ab und stellen Sie ihn beim nächsten Start wieder her. Entwerfen Sie die App nicht so, dass sie während des Herunterfahrens einen Bestätigungsdialog zeigt und wartet. Behandeln Sie die gewöhnliche Beendigungsbestätigung und eine Beendigungsanforderung vom Betriebssystem als getrennte Dinge.1
flowchart TB
accTitle: Die Arbeit beim Beenden klein halten
accDescr: Ein Entwurf, der alltäglich speichert, hinterlässt ein kleines Delta beim Beenden, während ein Entwurf, der bis zum Beenden im Speicher sammelt, nicht in die kurze Schonfrist passt und durch erzwungenes Beenden Verlust riskiert
good["Alltäglich speichern"] --> small["Das restliche Delta beim Beenden ist klein"]
small --> fast["Mit kurzem Aufräumen beenden"]
bad["Bis zum Beenden im Speicher sammeln"] --> large["Beim Beenden alles speichern"]
large --> risk["Zu wenig Schonfrist; Gefahr des erzwungenen Beendens"]
Abbildung 5: Die Vorbereitung, in wenigen Sekunden zu beenden, geschieht vor der Beendigungsbenachrichtigung.
3.3. In WinForms und WPF Speichern von nicht rückgängig machbarem Aufräumen trennen
In WinForms ist das Gegenstück FormClosing mit CloseReason.WindowsShutDown; in WPF ist es Application.SessionEnding. WPF kann es auch über das SessionEnding-Attribut in XAML registrieren, und es lässt sich durch Überschreiben von OnSessionEnding behandeln.
Beide sind jedoch Ereignisse der Abfragephase. Erlaubt ist hier höchstens ein „idempotentes Snapshot-Speichern“: unschädlich, wenn die Beendigung abgebrochen wird, und dasselbe Ergebnis, egal wie oft es läuft. Arbeit, die erst nach dem Commit der Beendigung möglich ist, etwa das Trennen, erledigen Sie, indem Sie WM_ENDSESSION mit wParam=TRUE in WndProc von WinForms oder einem WPF-Hook empfangen.
flowchart TB
accTitle: Aufteilung der Beendigungsbehandlung in WinForms und WPF
accDescr: FormClosing und SessionEnding speichern einen Snapshot, der auch bei Abbruch der Beendigung sicher ist, und ein Hook auf WM_ENDSESSION mit TRUE erledigt das nicht rückgängig machbare Aufräumen
notify["Abfragephase"] --> forms["FormClosing"]
notify --> wpf["SessionEnding"]
forms --> snapshot["Idempotentes Snapshot-Speichern"]
wpf --> snapshot
final["WM_ENDSESSION mit TRUE"] --> hook["In WndProc oder einem Hook empfangen"]
hook --> cleanup["Arbeit nach dem Commit, etwa Trennen"]
Abbildung 6: Stopfen Sie Arbeit nach dem Commit nicht in die Framework-Ereignisse.
Die nächsten zwei Beispiele sind der Teil, der in der Abfragephase nur den Arbeitszustand speichert. Die Codebeispiele sind Implementierungsauszüge; Inhalt der Speicherfunktion, Fehlerbehandlung, Ereignisregistrierung und so weiter liegen bei der App.
// WinForms: FormClosing wird auch bei Herunterfahren und Abmelden aufgerufen
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.WindowsShutDown)
{
// Nur ein idempotentes Snapshot-Speichern. Keinen Dialog zeigen.
// Auch e.Cancel = true (ablehnen) nicht setzen.
SaveWorkingStateToTempFile();
return;
}
// Im Normalfall, etwa wenn der Benutzer mit der Schaltfläche X schließt, darf hier bestätigt werden
}
// WPF: App.xaml.cs
protected override void OnSessionEnding(SessionEndingCancelEventArgs e)
{
base.OnSessionEnding(e);
// ReasonSessionEnding.Logoff / Shutdown lassen sich unterscheiden, aber
// der Grundansatz ist, für beide dasselbe Snapshot-Speichern auszuführen
SaveWorkingStateToTempFile();
// e.Cancel = true nur bei einem sehr guten Grund setzen
}
Führen Sie in beiden Fällen die Daten, die beim normalen Beenden, beim Herunterfahren und möglichst beim Absturz verwendet werden, in eine gemeinsame Speicherfunktion und ein gemeinsames Format. Wenn Sie für jeden Speicherpfad kein eigenes Format anlegen, kann die Wiederherstellungslogik beim nächsten Start ebenfalls eine einzige sein. Den Entwurf, beim Absturz Informationen zu hinterlassen, behandelt Design für Windows-Apps, das bei einem Absturz Protokolle und Dumps hinterlässt.
4. Nur für nicht unterbrechbare Arbeit vorübergehend blockieren
4.1. Eine Begründung zu registrieren und die Beendigung abzulehnen sind getrennte Aufgaben
Arbeit, die bei Unterbrechung physisch zerstört wird, etwa das Brennen einer CD oder das Schreiben von Firmware, ist die Ausnahme. Registrieren Sie mit ShutdownBlockReasonCreate eine Begründung, wenn die Arbeit beginnt, und heben Sie sie mit ShutdownBlockReasonDestroy auf, wenn sie fertig ist. Die registrierte Begründung erscheint auf dem Bildschirm der Apps, die das Herunterfahren verhindern.7
Beachten Sie jedoch: Eine Begründungszeichenfolge allein hält das Herunterfahren nicht an. Kombinieren Sie sie mit einem Schutzflag und geben Sie nur, solange es gesetzt ist, FALSE auf WM_QUERYENDSESSION zurück. Wenn die Arbeit fertig ist, heben Sie Begründung und Schutzflag beide auf.
flowchart TB
accTitle: Rollenverteilung bei einem vorübergehenden Beendigungsblock
accDescr: Nur während nicht unterbrechbarer Arbeit Begründung registrieren und die Abfrage ablehnen, beides bei Abschluss aufheben; ein erzwungenes Herunterfahren durch den Benutzer lässt sich trotzdem nicht verhindern
start["Nicht unterbrechbare Arbeit beginnt"] --> reason["Begründung registrieren und Schutzflag setzen"]
reason --> work["Auf einem Worker verarbeiten"]
work --> done["Fertig; Begründung und Flag aufheben"]
work -.-> request["Beendigungsabfrage kommt dazwischen"]
request --> refuse["FALSE zurückgeben und die Begründung zeigen"]
refuse --> choice{"Entscheidung des Benutzers"}
choice -->|"Abbrechen"| keep["Weiterlaufen"]
choice -->|"Erzwingen"| terminate["Kann beendet werden"]
Abbildung 7: Eine Begründung zu registrieren ist noch keine Ablehnung, und selbst die implementierte Ablehnung verhindert kein erzwungenes Herunterfahren.
4.2. Den UI-Thread in einem Zustand halten, in dem er die Beendigungsanforderung empfangen kann
Registrieren und aufheben Sie die Begründung vom Thread, der das Zielfenster erzeugt hat. Aufrufe von anderen Threads schlagen fehl.7 Die lange nicht unterbrechbare Arbeit selbst wandert dagegen auf einen Worker-Thread. Wenn der UI-Thread durch synchrone Verarbeitung blockiert ist, wird die App „Keine Rückmeldung“, bevor die Nachricht, die zum Ablehnen nötig ist, überhaupt verarbeitet wird.
[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);
// Vom Thread aufrufen, der das Hauptfenster erzeugt hat (Aufrufe von anderen Threads schlagen fehl)
_criticalOperationInProgress = true;
ShutdownBlockReasonCreate(this.Handle, "Messdaten werden in eine Datei geschrieben");
try
{
// Die nicht unterbrechbare Arbeit auf einem Worker-Thread ausführen. Synchrone Ausführung
// auf dem UI-Thread stoppt die Nachrichtenpumpe, und die App wird als
// „Keine Rückmeldung“ zwangsfortgesetzt, bevor der Ablehnungscode für WM_QUERYENDSESSION unten laufen kann
await Task.Run(() => WriteMeasurementData());
}
finally
{
ShutdownBlockReasonDestroy(this.Handle);
_criticalOperationInProgress = false;
}
// Zusätzlich nur während des Schutzes FALSE auf WM_QUERYENDSESSION zurückgeben, um abzulehnen
protected override void WndProc(ref Message m)
{
const int WM_QUERYENDSESSION = 0x0011;
if (m.Msg == WM_QUERYENDSESSION && _criticalOperationInProgress)
{
m.Result = IntPtr.Zero; // Ablehnen. Die registrierte Begründungszeichenfolge erscheint in der Vollbild-UI
return;
}
base.WndProc(ref m);
}
Dieses Beispiel ist das Gerüst, das Registrieren der Begründung, Verarbeitung auf einem Worker und Ablehnen der Abfrage kombiniert. Fehlerbehandlung einschließlich API-Fehlern ist gesondert nötig. Halten Sie die Begründungszeichenfolge kurz und konkret, und beschränken Sie die Registrierung auf die Zeit, in der die Arbeit wirklich nicht unterbrochen werden kann, statt sie die ganze Lebensdauer der App registriert zu lassen.7
Trotzdem kann der Benutzer die Fortsetzung erzwingen. Es gibt auch erzwungene Beendigungspfade wie ENDSESSION_CRITICAL, machen Sie also nie die Fähigkeit zu blockieren zur Voraussetzung der Datenintegrität. Die Vorsorge für den Fall, dass Sie es nicht anhalten konnten, ist der Speicher- und Wiederherstellungsentwurf von Kapitel 8.6
5. Konsole und .NET — die Bedingungen prüfen, unter denen Benachrichtigungen ankommen
5.1. Benachrichtigungen und Schonfristen über SetConsoleCtrlHandler
In einer Konsolen-App kommen Steuersignale beim Handler an, der mit SetConsoleCtrlHandler registriert ist. Anders als die Nachrichtenbehandlung der GUI läuft der Handler auf einem getrennten Thread.2
| Signal | Wann es auftritt | Standardschonfrist |
|---|---|---|
CTRL_C_EVENT / CTRL_BREAK_EVENT |
Ctrl+C / Ctrl+Break | Kein ausdrückliches Timeout |
CTRL_CLOSE_EVENT |
Schließen der Konsole, „Task beenden“ im Task-Manager und so weiter | Etwa 5 Sekunden |
CTRL_SHUTDOWN_EVENT |
Dienstprozesse beim Systemherunterfahren | Etwa 20 Sekunden |
Ein Prozess, der etwa über die Registerkarte „Details“ des Task-Managers zwangsbeendet wird, endet sofort ohne Benachrichtigung und liegt außerhalb dieser Tabelle.2
Entwerfen Sie eine Konsolen-App in einer interaktiven Sitzung nicht so, dass sie auf CTRL_LOGOFF_EVENT oder CTRL_SHUTDOWN_EVENT wartet. Interaktive Apps werden im Moment der Abmeldung beendet, praktisch können also nur Prozesse, die als Dienst laufen, diese empfangen. Außerdem wird ein Prozess, der gdi32.dll oder user32.dll lädt, als Windows-App behandelt, und die LOGOFF/SHUTDOWN-Handler werden nicht aufgerufen. Die offizielle Umgehung ist in dem Fall, ein verstecktes Fenster zu erzeugen und WM_QUERYENDSESSION / WM_ENDSESSION zu empfangen.28
flowchart TB
accTitle: Bedingungen, unter denen Beendigungsbenachrichtigungen der Konsole ankommen
accDescr: Eine interaktive Konsole kann die Schließbenachrichtigung empfangen, aber nicht mit Abmelde- oder Herunterfahrbenachrichtigungen rechnen, und selbst ein Dienst braucht einen anderen Benachrichtigungspfad, sobald er die GUI-DLLs geladen hat
console["Konsolenprozess"] --> close["Schließen geht an den Steuerhandler"]
console --> q{"Auf LOGOFF oder SHUTDOWN warten?"}
q -->|"Interaktive Sitzung"| no["Auf diese Benachrichtigung ist kein Verlass"]
q -->|"Dienst"| dll{"GUI-DLLs geladen?"}
dll -->|"Nein"| signal["Das betreffende Steuersignal behandeln"]
dll -->|"Ja"| window["Über ein verstecktes Fenster empfangen"]
Abbildung 8: Behandeln Sie den Umgang mit dem Schließen der Konsole nicht als Umgang mit dem Herunterfahren.
Der nächste Auszug bereitet auf Ctrl+C und das Schließen der Konsole vor. Er hält den Delegaten fest, damit der GC ihn nicht einsammelt, und geht nach kurzem Aufräumen zum Standardhandler weiter. Allein diese Registrierung garantiert einer interaktiven App keine Herunterfahrbenachrichtigungen.
// Konsolen-App: bei Ctrl+C und dem Schließen der Konsole aufräumen
[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; // Referenz halten, damit der GC nicht einsammelt
static bool OnCtrlEvent(int ctrlType)
{
// Nur Aufräumen, das innerhalb von 5 Sekunden fertig ist
FlushAndCloseDataFile();
return false; // Zum Standardhandler weitergehen; der Prozess beendet
}
static void Main()
{
SetConsoleCtrlHandler(s_handler, add: true);
// ...
}
5.2. Ab .NET 10 Aufräumen nicht nur in ProcessExit legen
Ab .NET 10 stellt die Laufzeit keinen Standardhandler mehr für CTRL_CLOSE_EVENT / CTRL_SHUTDOWN_EVENT bereit. Ohne einen eigenen Handler oder einen einer übergeordneten Bibliothek beendet die Standardbehandlung des Betriebssystems den Prozess, und auf diesem Pfad feuern weder AppDomain.ProcessExit noch AssemblyLoadContext.Unloading.4
Das ist eine Änderung am Standardverhalten für externe Beendigungssignale. Es bedeutet nicht, dass ProcessExit bei einem normalen Beenden wie der Rückkehr aus Main nicht mehr feuert.
flowchart TB
accTitle: Die Abhängigkeit vom Standard-Beendigungshandler von .NET lösen
accDescr: Die ältere Laufzeit verband die betreffenden Signale mit ProcessExit, ab .NET 10 gibt es keinen Standardhandler mehr, also den Benachrichtigungspfad des App-Modells nutzen
signal["CLOSE- oder SHUTDOWN-Signal"] --> old["Standardbehandlung der älteren Laufzeit"]
old --> event["Löst ProcessExit und Ähnliches aus"]
signal --> modern["Ab .NET 10 keine Standardbehandlung"]
modern --> own{"Behandelt die App es?"}
own -->|"Nein"| os["Durch OS-Standardbehandlung beendet"]
own -->|"Ja"| handle["Aufräumen, das zum Modell passt"]
Abbildung 9: Unterscheiden Sie ein normales Beenden von der Beendigung durch ein externes Signal, und stellen Sie den Handler bereit, den Ihr App-Modell braucht.
Bündeln Sie das Aufräumen dort, wo es zum App-Modell passt. Für eine GUI sind das die Ereignisse und die Commit-Benachrichtigung von Kapitel 3; für Generic Host IHostApplicationLifetime und BackgroundService.StopAsync; für eine reine Konsolen-App SetConsoleCtrlHandler oder PosixSignalRegistration. Wenn Sie Letzteres nutzen, wählen Sie die Signale, die zu den anvisierten Beendigungspfaden passen, etwa SIGINT, SIGTERM und SIGHUP.4
Setzen Sie in Generic Host HostOptions.ShutdownTimeout ausdrücklich. Die Einstellung auf der Host-Seite allein verlängert jedoch nicht die äußere Schonfrist von GUI, Konsole oder SCM. Auch wenn Ctrl+C kein ausdrückliches Timeout hat, müssen Sie für Stromausfall und andere Beendigungspfade vorsorgen. In jedem Modell gilt dieselbe Linie: den Zustand gespeichert halten und die Arbeit nach der Benachrichtigung klein halten.
flowchart TB
accTitle: Die Stoppverarbeitung des Hosts und die äußere Beendigungsschonfrist
accDescr: Der Stopp von Generic Host ist in StopAsync gebündelt, aber das Timeout von HostOptions verlängert nicht die Beendigungsschonfrist von OS oder SCM, also beides prüfen
outer["Beendigungsanforderung von OS oder SCM"] --> host["Stoppfad des Hosts"]
host --> stop["In StopAsync aufräumen"]
stop --> inner["Die Stoppzeit auf der Host-Seite setzen"]
outer -.-> limit["Die äußere Schonfrist existiert getrennt"]
inner --> check["Messen, ob es schnell fertig wird"]
limit --> check
Abbildung 10: Prüfen Sie die Zeitgrenzen auf Host-Seite und OS-Seite getrennt, und geben Sie sich nicht mit dem Verlängern der Einstellung zufrieden.
6. Windows-Dienste — aus dem Steuerhandler sofort zurückkehren
6.1. Der Unterschied zwischen SHUTDOWN und PRESHUTDOWN
Dienste stoppen beim Abmelden nicht, werden aber beim Herunterfahren und Neustart gestoppt. Die Benachrichtigung kommt vom SCM (Dienststeuerungs-Manager), und der Dienst muss das Flag erklären, das zum gewünschten Steuercode gehört.3
| Zu erklärendes Flag | Gelieferter Steuercode | Wann Sie es nutzen |
|---|---|---|
SERVICE_ACCEPT_SHUTDOWN |
SERVICE_CONTROL_SHUTDOWN |
Die gewöhnliche Herunterfahrbenachrichtigung. Die Standardschonfrist beträgt etwa 20 Sekunden und hängt von WaitToKillServiceTimeout ab |
SERVICE_ACCEPT_PRESHUTDOWN |
SERVICE_CONTROL_PRESHUTDOWN |
Wird vor dem gewöhnlichen SHUTDOWN geliefert. Der SCM wartet, bis der Dienst stoppt oder das konfigurierte Timeout abläuft |
flowchart TB
accTitle: Stufen der Beendigungsbenachrichtigung an Dienste
accDescr: Der SCM benachrichtigt zuerst Dienste, die PRESHUTDOWN annehmen, wartet auf Stopp oder Timeout und geht dann zur gewöhnlichen SHUTDOWN-Benachrichtigung weiter
start["Systemherunterfahren beginnt"] --> pre["Dienste benachrichtigen, die PRESHUTDOWN annehmen"]
pre --> wait["Auf Stopp oder die konfigurierte Frist warten"]
wait --> shut["Dienste benachrichtigen, die SHUTDOWN annehmen"]
shut --> proceed["Nach der Schonfrist zum Systemende weitergehen"]
Abbildung 11: Früh benachrichtigt zu werden und abgewartet zu werden sind getrennte Bedingungen; auch PRESHUTDOWN hat eine konfigurierte Frist.
Das PRESHUTDOWN-Timeout wird mit ChangeServiceConfig2(SERVICE_CONFIG_PRESHUTDOWN_INFO) konfiguriert. Der Standard ist 10 Sekunden ab Windows 10 Creators Update (Build 15063) und 3 Minuten davor. Wenn Sie annehmen „PRESHUTDOWN kauft immer 3 Minuten“, bekommen Sie nicht die Zeit, mit der Sie gerechnet haben.9
Weil PRESHUTDOWN das Herunterfahren des gesamten Systems aufhält, nutzen Sie es nur, wenn es wirklich nötig ist. Das gewöhnliche WaitToKillServiceTimeout von der Dienstseite umzuschreiben, um es zu verlängern, wird ebenfalls nicht empfohlen.3
6.2. Den Empfang der Benachrichtigung vom tatsächlichen Stoppen trennen
Der Steuerhandler muss innerhalb von 30 Sekunden zurückkehren, aber statt das als 30 Sekunden zu denken, die Sie nutzen dürfen, strukturieren Sie ihn so, dass er den Stopp signalisiert und sofort zurückkehrt. Übergeben Sie die lange Arbeit einem anderen Thread und melden Sie SERVICE_STOP_PENDING.3
// Win32-Dienst: PRESHUTDOWN annehmen und die Stopparbeit einem Worker überlassen
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); // Dem Worker den Stopp signalisieren und sofort zurückkehren
return NO_ERROR;
}
return ERROR_CALL_NOT_IMPLEMENTED;
}
// Worker-Seite: dauert das Aufräumen länger als der Wartehinweis, SERVICE_STOP_PENDING
// regelmäßig weiter melden und dwCheckPoint erhöhen. Der SCM urteilt anhand von Wartehinweis
// und fortschreitendem Prüfpunkt, dass der Dienst „noch lebt und vorankommt“. Stoppen die
// Meldungen, gilt er als hängend, und das Herunterfahren kann weitergehen. Bei Abschluss
// immer SERVICE_STOPPED melden
Dauert das Aufräumen auf der Worker-Seite länger als der Wartehinweis, melden Sie SERVICE_STOP_PENDING weiter und erhöhen Sie dwCheckPoint. Stoppen die Fortschrittsmeldungen, kann der Dienst als hängend gelten. SERVICE_STOPPED bei Abschluss zu melden, gehört zur Stoppverarbeitung.39
flowchart TB
accTitle: Aufteilung der Arbeit zwischen Dienststeuerhandler und Worker
accDescr: Der Steuerhandler meldet Stopp ausstehend, signalisiert den Worker und kehrt sofort zurück; der Worker räumt auf, meldet Fortschritt und meldet am Ende gestoppt
handler["Steuerhandler"] --> pending["STOP_PENDING melden"]
pending --> signal["Dem Worker den Stopp signalisieren"]
signal --> back["Der Handler kehrt sofort zurück"]
signal --> worker["Der Worker räumt auf"]
worker --> report["Bei Dauer Fortschritt melden"]
report --> stopped["Bei Abschluss STOPPED melden"]
Abbildung 12: Blockieren Sie den Thread, der die Benachrichtigung empfängt, nicht mit langer Stoppverarbeitung.
6.3. Auch dann schnell fertig werden können, wenn eine Abhängigkeit zuerst stoppt
Beim Herunterfahren sendet der SCM Benachrichtigungen standardmäßig ohne Rücksicht auf Dienstabhängigkeiten. Die Stoppverarbeitung muss den Fall behandeln, dass ein abhängiger Dienst bereits nicht mehr verfügbar ist, und darf nicht zu lange auf Antworten von Netzwerkgegenstellen warten. Statt Zeit für Speicherfreigabe und Ähnliches zu verwenden, hat Vorrang, die nötigen Daten schnell zu bestätigen.3
Diese Linie ist auch im Verhältnis zu einer USV wichtig. Je länger ein Dienst sein Warten verlängert, desto schwerer wird es, das Herunterfahren des gesamten Betriebssystems zu beenden, bevor der Akku leer ist. Statt die Schonfrist zu erhöhen, verringern Sie die Arbeit beim Stoppen, indem Sie an jedem Meilenstein speichern.3
Wenn ein .NET-Worker-Service unter UseWindowsService läuft, werden STOP / SHUTDOWN in einen Host-Stopp umgewandelt, der zu BackgroundService.StopAsync führt. Die Standardimplementierung zum Zeitpunkt des Originalartikels nimmt PRESHUTDOWN nicht an; wenn Sie es brauchen, müssen Sie die Handlerimplementierung erweitern. Die Linie, HostOptions.ShutdownTimeout ausdrücklich zu setzen und StopAsync selbst in wenigen Sekunden zu beenden, ist dieselbe. Zur Implementierung insgesamt siehe Windows-Dienste erstellen und betreiben.
7. Wiederaufnahme nach einem Neustart — Registrierung, Wiederherstellungsdaten und Anmeldung zusammenbringen
7.1. RegisterApplicationRestart registriert den Wiederaufnahmepfad im Voraus
Auf Geräte-PCs und im unbeaufsichtigten Betrieb geht der Entwurf über Speichern und Beenden hinaus: Er umfasst, die Arbeit nach dem Neustart wieder aufzunehmen. RegisterApplicationRestart ist die API, die die App für den Neustart in jeder dieser Situationen registriert: Absturz (unbehandelte Ausnahme), keine Rückmeldung, App-Neustart durch ein Update und OS-Neustart durch ein Update.10
Sie können Kommandozeilenargumente für den Neustart angeben, etwa geöffnete Dateien oder einen Wiederherstellungspunkt. Allein zu registrieren bedeutet jedoch nicht automatische Wiederherstellung aus jeder Situation.
| Prüfpunkt | Bedingung oder Einschränkung |
|---|---|
| Zeitpunkt der Registrierung | Bevor ein Problem auftritt. Im Update-Szenario ist die Behandlung von WM_QUERYENDSESSION die letzte Gelegenheit |
| Verhinderung einer Neustartschleife | Ein Prozess, der weniger als 60 Sekunden gelaufen ist, wird nicht neu gestartet |
| Absturz oder Hänger | Nach Zustimmung des Benutzers neu gestartet. Ein Neustart durch ein Update ist automatisch |
| Über einen OS-Neustart hinweg | Die auslösende Seite muss die Shutdown-API mit EWX_RESTARTAPPS / SHUTDOWN_RESTARTAPPS aufrufen |
| Erhöhter Prozess | Nicht für automatischen Neustart berechtigt, also ist ein getrennter ausdrücklicher Startpfad nötig |
Für eine App, die Erhöhung braucht, entwerfen Sie den Wiederaufnahmepfad, indem Sie die UI mit Standardrechten laufen lassen und die privilegierte Arbeit in einen Dienst auslagern, oder indem Sie eine Aufgabe der Aufgabenplanung mit „Mit höchsten Privilegien ausführen“ oder Ähnlichem nutzen.10
7.2. Wiederherstellungsrückruf und ARSO haben jeweils andere Rollen
Wenn Sie zusätzlich RegisterApplicationRecoveryCallback nutzen, ruft WER (Windows-Fehlerberichterstattung) beim Absturz den Wiederherstellungsrückruf auf, und Sie können die gerade bearbeiteten Daten speichern. Solange das Speichern weiterläuft, rufen Sie ApplicationRecoveryInProgress innerhalb des registrierten Ping-Intervalls auf und benachrichtigen Sie ApplicationRecoveryFinished bei Abschluss. Stoppen die Fortschrittsmeldungen, kann die Wiederherstellungsverarbeitung abgebrochen werden.
flowchart TB
accTitle: Speichern und Fortschrittsmeldung im Wiederherstellungsrückruf
accDescr: Der von WER aufgerufene Wiederherstellungsrückruf ruft während des Speicherns ApplicationRecoveryInProgress innerhalb des Ping-Intervalls weiter auf und benachrichtigt ApplicationRecoveryFinished bei Abschluss
reg["Wiederherstellungsrückruf im Voraus registrieren"] --> crash["Absturz; WER ruft ihn auf"]
crash --> save["Die gerade bearbeiteten Daten speichern"]
save --> progress["Fortschritt innerhalb des Ping-Intervalls melden"]
progress --> completed{"Speichern abgeschlossen?"}
completed -->|"Noch nicht"| save
completed -->|"Fertig"| done["Wiederherstellung als fertig melden"]
progress -.-> timeout["Kann abgebrochen werden, wenn Meldungen stoppen"]
Abbildung 13: Speichern Sie nicht nur; benachrichtigen Sie WER über Fortschritt und Abschluss.
Dateien, die während eines Updates in Verwendung sind, auszutauschen und neu zu starten, ist die Aufgabe von Restart Manager. Das behandelt Wie man eine exe oder DLL austauscht, die gerade verwendet wird.
Der Mechanismus, der die Benutzersitzung nach einem OS-Neustart zurückbringt, ist dagegen ARSO (automatische Neustart-Anmeldung von Winlogon). Wenn Windows Update einen automatischen Neustart beginnt, speichert es die Anmeldeinformationen des letzten interaktiven Benutzers sicher und konfiguriert Autologon, und nach dem Neustart meldet es diesen Benutzer an und sperrt den Bildschirm.11
flowchart TB
accTitle: Neustartregistrierung, Anmeldung und Datenwiederherstellung
accDescr: Gegenüber einer vorherigen Neustartregistrierung braucht ein Absturz die Zustimmung des Benutzers, und Benutzer-Apps nach einem OS-Neustart brauchen die Sitzung über ARSO oder Ähnliches und den gespeicherten Zustand wiederhergestellt
reg["Vor einem Problem für den Neustart registrieren"] --> crash["Absturz oder keine Rückmeldung"]
crash --> consent["Zustimmung des Benutzers einholen"]
consent --> app["Die App neu starten"]
reg --> update["OS-Neustart mit den erforderlichen Flags"]
update --> session["Die Sitzung über ARSO oder Ähnliches zurückbringen"]
session --> app
app --> data["Den gespeicherten Wiederherstellungspunkt lesen"]
session -.-> policy["Richtlinie und Startbedingungen prüfen"]
Abbildung 14: Stellen Sie nicht nur die Neustartregistrierung der App bereit, sondern auch den Pfad, auf dem Sitzung und Arbeitszustand zurückkommen.
shutdown /g ist der Befehl, der einen Neustart plus die Wiederaufnahme registrierter Apps anfordert. ARSO kann durch eine Organisationsrichtlinie wie DisableAutomaticRestartSignOn deaktiviert sein, prüfen Sie das also zusammen mit den Anforderungen der unbeaufsichtigten Wiederaufnahme. Hintergrundverarbeitung, die immer nötig ist, läuft besser als Windows-Dienst, als dass sie von der automatischen Anmeldung des Benutzers abhängt.11
8. Stromausfall ohne Benachrichtigung — Speichern und Laden als Paar entwerfen
8.1. Temporäre Datei, Tausch, Sicherung und Prüfung beim Start als einen Satz
Ein ausgelöster Schutzschalter, ein ausgefallenes Netzteil oder ein gezogener Stecker bringt weder WM_ENDSESSION noch PRESHUTDOWN. Wenn Sie die Originaldatei an Ort und Stelle überschreiben, kann eine Unterbrechung dazwischen eine Datei hinterlassen, in der alte und neue Inhalte gemischt sind.
Die Basis ist, vollständig in eine temporäre Datei auf demselben Volume zu schreiben, zu leeren und dann zu tauschen. ReplaceFile bündelt die Schritte, die dem Speichern in eine neue Datei, dem Beiseitelegen des Originals, dem Umbenennen und dem Löschen entsprechen, und übernimmt Attribute wie Erstellungszeit, ACL und alternative Datenströme. Die zu ersetzende Datei, die Ersatzdatei und die Sicherung müssen auf demselben Volume liegen. File.Replace in .NET ruft diese API auf.12
flowchart TB
accTitle: Eine Datei speichern und beim nächsten Start wiederherstellen
accDescr: In eine temporäre Datei auf demselben Volume schreiben, leeren, tauschen und eine Sicherung behalten, dann die Primärdatei beim Start prüfen und bei Bedarf auf die Sicherung zurückfallen
tmp["Temporäre Datei auf demselben Volume"] --> write["Bis zum Ende schreiben und leeren"]
write --> replace["Tauschen; alter Inhalt geht nach .bak"]
replace -.-> boot["Nächster Start"]
boot --> valid{"Ist die Primärdatei intakt?"}
valid -->|"Ja"| main["Die Primärdatei lesen"]
valid -->|"Nein"| backup["Auf die Sicherung zurückfallen"]
Abbildung 15: Implementieren Sie nicht nur die sichere Schreibweise, sondern auch die Leseweise, wenn die Datei beschädigt ist.
// Das Standardmuster zum Speichern von Einstellungen und Daten: in eine temporäre Datei schreiben, dann tauschen und den alten Inhalt behalten
public static void SaveAtomically(string path, string content)
{
string dir = Path.GetDirectoryName(path)!;
string tmp = Path.Combine(dir, Path.GetRandomFileName()); // Auf demselben Volume anlegen
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); // Entspricht FlushFileBuffers. Schreibt die OS-Puffer
// auf den Datenträger (Grenzen des geräteseitigen Caches in Abschnitt 8.2)
}
if (File.Exists(path))
File.Replace(tmp, path, path + ".bak"); // Ruft ReplaceFile auf. Behält den alten Inhalt als .bak
else
File.Move(tmp, path);
}
catch
{
// Bei einem Abbruch dazwischen die temporäre Datei nicht hinterlassen. Wenn periodisches
// Speichern weiter fehlschlägt, würden vollständige Kopien nach und nach das Volume füllen
try { File.Delete(tmp); } catch { /* Ein fehlgeschlagenes Löschen weicht der ursprünglichen Ausnahme */ }
throw;
}
}
Auch wenn dieses Beispiel SaveAtomically heißt, garantiert es keine Atomizität über einen Stromausfall hinweg. Im normalen Betrieb bleibt entweder die vollständige alte oder die vollständige neue Datei lesbar, aber ReplaceFile ist eine mehrstufige Namensraumoperation, und ihre Atomizität gegen einen plötzlichen Stromausfall ist spezifikationsseitig nicht garantiert. Genau deshalb behalten Sie die .bak und implementieren Sie die Laderoutine, die die Primärdatei beim Start prüft und bei Beschädigung auf die Sicherung zurückfällt.12
Der catch im Beispiel existiert, damit temporäre Dateien bei gewöhnlichen Fehlern nicht anhäufen und das Volume füllen. Er erwartet nicht, dass dieser Code im Moment eines Stromausfalls läuft. Für nur anhängende Protokolle und CSVs wenden Sie den Tausch der ganzen Datei nicht unverändert an; nutzen Sie ein Format, das die Bruchstelle einbezieht, etwa „eine Zeile je Datensatz schreiben und eine beschädigte letzte Zeile beim Laden verwerfen“.
8.2. Erfolg von WriteFile vom Erreichen des Datenträgers unterscheiden
Selbst wenn WriteFile Erfolg hat, können die Daten noch im OS-Cache sitzen. Windows schreibt normalerweise in die Systempuffer und bringt die Daten mit verzögertem Schreiben auf den Datenträger. An wichtigen Prüfpunkten leeren Sie mit FlushFileBuffers, oder geben Sie bei CreateFile FILE_FLAG_WRITE_THROUGH an, um ein sofortiges Schreiben anzufordern. Auch Dateisystemmetadaten werden zwischengespeichert, das Bestätigen involviert also ein Leeren oder Write-Through.13
Write-Through ist jedoch nicht dasselbe wie „den OS-Cache nicht zu nutzen“. Häufige Aufrufe von FlushFileBuffers sind ineffizient, erwägen Sie also bei Bedarf die Kombination mit FILE_FLAG_NO_BUFFERING. In der Praxis ist der realistische Entwurf, an den für die Integrität wichtigen Stellen zu leeren, etwa an Transaktionsgrenzen und unmittelbar vor dem Schließen der Datei.13
flowchart TB
accTitle: Die Grenze zwischen Schreiberfolg und Dauerhaftigkeit
accDescr: Ein gewöhnliches Schreiben erreicht den Speicher vom OS-Cache mit Verzögerung; Leeren oder Write-Through drängt auf Bestätigung, aber die Grenzen des geräteseitigen Caches bleiben
write["WriteFile hat Erfolg"] --> cache["Kann im OS-Cache liegen"]
cache --> delayed["Verzögertes Schreiben"]
cache --> flush["An einem Prüfpunkt leeren"]
delayed --> device["Auf den Speicher angewendet"]
flush --> device
device -.-> limit["Grenzen des geräteseitigen Caches"]
Abbildung 16: Behandeln Sie API-Erfolg, Bestätigung des OS-Caches und Widerstand gegen Stromausfall nicht als dasselbe.
Auch der flüchtige Cache auf der Geräteseite hat Grenzen, und Sie können nicht sagen „wir haben geleert, also übersteht es auf jeder Hardware einen Stromausfall vollständig“. Das Verhältnis von Cache-Manager, verzögertem Schreiben und Hardwarecaches erklärt ausführlich Die Tiefen von Windows-I/O (Teil 4) — Cache-Manager: Wann erreicht Ihr WriteFile tatsächlich die Festplatte?.
8.3. Die USV nutzen, um einen Stromausfall in ein geplantes Herunterfahren zu überführen
Die Rolle einer USV ist nicht, Ausfälle zu beseitigen, sondern einen Stromausfall ohne Benachrichtigung in ein geplantes Herunterfahren mit Benachrichtigung zu verwandeln. Die Bedingung, die Sie brauchen, ist dieses Verhältnis:
Akkulaufzeit der USV > Zeit zum Erkennen des Wechsels + Aufräumen von Apps und Diensten + Abschluss des OS-Herunterfahrens
Der Wechsel zwischen Netzstrom und Akku und ein niedriger Restladestand werden über PBT_APMPOWERSTATUSCHANGE gemeldet. Eine GUI empfängt das über WM_POWERBROADCAST; ein Dienst erklärt SERVICE_ACCEPT_POWEREVENT und empfängt dann SERVICE_CONTROL_POWEREVENT in HandlerEx. WM_POWERBROADCAST wird nicht an den Steuerhandler eines Dienstes geliefert.14
Nach dem Empfang prüfen Sie ACLineStatus und BatteryLifePercent mit GetSystemPowerStatus und gehen zum Unterbrechen der Messung, zum Speichern und zum Anfordern des Herunterfahrens weiter.14
flowchart TB
accTitle: Von der USV-Erkennung bis zum Abschluss des Herunterfahrens
accDescr: Den Wechsel der USV auf Akku über den passenden Benachrichtigungspfad erkennen, den Energiezustand prüfen, zu Speichern und Herunterfahren weitergehen und die gesamte Folge in die Akkulaufzeit legen
outage["Ausfall; die USV wechselt auf Akku"] --> notice["Energiebenachrichtigung auf dem passenden Pfad"]
notice --> check["Energiezustand und Restladung prüfen"]
check --> save["Messung unterbrechen und speichern"]
save --> req["Beim OS ein Herunterfahren anfordern"]
req --> done["Aufräumen und OS-Ende abschließen"]
done -.-> time["Die gesamte Folge in die Laufzeit legen"]
Abbildung 17: Legen Sie nicht nur die App, sondern die Zeit bis zum Ende des Betriebssystems in die Laufzeit der USV.
In Konfigurationen, in denen Windows die USV als Akku sieht, etwa eine typische USB-USV, können die Standard-APIs sie überwachen. Wenn die Verwaltungssoftware des Herstellers eine Funktion „bei N % Restladung herunterfahren“ hat, prüfen Sie auch, dass ihre Schwelle zur Aufräumzeit passt. Die Wiederaufnahme aus Energiesparmodus oder Ruhezustand ist ein getrenntes Thema; siehe Energiesparmodus, Ruhezustand, Modern Standby und lang laufende Anwendungen.
9. Prüfung — Benachrichtigungen, Zeit und Wiederherstellungsergebnis bestätigen
9.1. Die Beendigungspfade auf einer Prüfmaschine reproduzieren, nicht in der Produktion
Probieren Sie Dinge nicht zuerst auf dem Produktions-Geräte-PC; nutzen Sie eine Prüfmaschine oder eine virtuelle Maschine wie Hyper-V. Auf einer virtuellen Maschine nehmen Sie vorher einen Prüfpunkt und wiederholen Sie die Tests mit Prüfdaten, die Sie verlieren können.
| Zu versuchender Vorgang | Was zu bestätigen ist |
|---|---|
| Abmelden | WM_QUERYENDSESSION → WM_ENDSESSION der GUI und die Speicherverarbeitung. Kein Ersatz für die Prüfung eines Dienststopps |
shutdown /s /t 0 |
Verhalten bei einem vollständigen Herunterfahren |
shutdown /s /hybrid /t 0 |
Das Hybridverhalten in einer Konfiguration, die Schnellstart nutzt |
shutdown /r /t 0 |
Ein Neustart mit vollständigem Start und die Wiederaufnahme danach |
| Ausschalten der VM | Ob der nächste Start wiederherstellt, auch wenn das Gast-OS ohne Benachrichtigung stoppt |
| Stromausfall auf produktionsgleicher Hardware | Widerstand einschließlich physischem Speicher und Controller |
Eine Abmeldung unterscheidet sich dadurch, dass das Bit ENDSESSION_LOGOFF gesetzt ist, ist aber ein einfacher Weg, den Benachrichtigungspfad der GUI zu bestätigen. Verwechseln Sie die Befehle für vollständig, hybrid und Neustart nicht; versuchen Sie sie getrennt.15
flowchart TB
accTitle: Die Prüfung des Herunterfahrens stufenweise erweitern
accDescr: In der Prüfumgebung GUI-Benachrichtigungen und jeden Beendigungsvorgang versuchen, die Wiederherstellung nach einem plötzlichen Stopp auf einer VM bestätigen, dann den Widerstand gegen Stromausfall einschließlich Speicher auf produktionsgleicher Hardware prüfen
prep["Prüfmaschine und Daten vorbereiten"] --> notify["Benachrichtigungspfade und Beendigungsvorgänge versuchen"]
notify --> time["Die Aufräumzeit messen"]
time --> vm["Wiederherstellung nach einem plötzlichen VM-Stopp bestätigen"]
vm --> real["Auf echter Hardware einschließlich Speicher prüfen"]
real --> check["Daten und Wiederaufnahme beim nächsten Start bestätigen"]
Abbildung 18: Prüfen Sie nicht nur, ob die App normal beendet, sondern auch, was sie nach einem plötzlichen Stopp wiederherstellen kann.
Das Ausschalten einer VM reproduziert nur, dass der Gast ohne Vorwarnung stoppt. Es kann den Verlust des flüchtigen Caches einer physischen Festplatte oder controllerabhängige Beschädigung nicht reproduzieren; beim Versand als Geräte-PC führen Sie die Endbestätigung also auf produktionsgleicher Hardware durch.
Protokollieren Sie einen Zeitstempel am Anfang und Ende der Aufräumfunktion und messen Sie, ob sie in die etwa 5 Sekunden für eine GUI und Ähnliches oder in die für den Dienst konfigurierte Schonfrist passt. Bestätigen Sie nicht nur, dass die Beendigung gelang, sondern auch was beim nächsten Start geladen wurde und von wo die Arbeit wieder aufgenommen werden konnte.
9.2. Was nachts passiert ist, aus dem Ereignisprotokoll isolieren
Im Windows-Systemprotokoll zeichnet Ereignis-ID 1074 den Prozess auf, der das Herunterfahren ausgelöst hat, den Benutzer und den Grund. Bei einem unerwarteten Herunterfahren wird beim nächsten Start 41 (Kernel-Power) oder 6008 aufgezeichnet.15
# Die jüngste Historie herunterfahrbezogener Ereignisse prüfen
Get-WinEvent -FilterHashtable @{ LogName = 'System'; Id = 1074, 6008, 41 } -MaxEvents 20 |
Select-Object TimeCreated, Id, ProviderName, Message |
Format-List
Wenn 1074 einen Windows-Update-Neustart zeigt und die Daten trotzdem beschädigt waren, untersuchen Sie zuerst die Behandlung der Beendigungsbenachrichtigung und den Speicherpfad. Schließen Sie andererseits aus 41 oder 6008 allein nicht auf Stromausfall. Sie zeigen eine unerwartete Beendigung, und ein Bluescreen oder ein erzwungener Reset sind ebenfalls Kandidaten.15
flowchart TB
accTitle: Aus dem Herunterfahr-Ereignisprotokoll wählen, was zu untersuchen ist
accDescr: Auslösenden Prozess und Grund aus 1074 bestätigen und 41 und 6008 als Hinweise auf eine unerwartete Beendigung mit Umgebungsinformationen wie Bugcheck-Code und Dumps abgleichen
log["Systemereignisprotokoll"] --> normal["1074: auslösender Prozess und Grund"]
normal --> cleanup["Benachrichtigungsbehandlung und Speicherpfad untersuchen"]
log --> unexpected["41 und 6008: unerwartete Beendigung"]
unexpected --> evidence["BugcheckCode und Dumps prüfen"]
evidence --> classify["Absturz, Stromausfall und so weiter isolieren"]
Abbildung 19: 41 und 6008 sind kein Beweis für einen Stromausfall selbst; sie sind der Einstieg in die weitere Untersuchung.
Ein BugcheckCode ungleich null in Ereignis 41 ist ein Hinweis auf einen Absturz. Ist er 0 und gibt es keinen Speicherabbild, steht Stromausfall im Verdacht, entscheiden Sie aber durch Abgleich mit den Umgebungsinformationen. Stellt sich Stromausfall heraus, konzentrieren Sie sich auf den Speicherentwurf und die USV von Kapitel 8; war es eine geplante Beendigung, auf die Benachrichtigungen und das Aufräumen der Kapitel 3 bis 6.
10. Zusammenfassung — nicht nur die Beendigungsbehandlung, sondern bis zum nächsten Start entwerfen
Der Umgang mit dem Herunterfahren ist nicht die Geschichte eines Ereignishandlers, der einmal beim Beenden läuft. Wichtig ist, alltägliches Speichern → kurzes Aufräumen → Prüfung und Wiederherstellung beim nächsten Start zu einem durchgehenden Ganzen zu machen.
| Wo nachzuschauen ist | Entwurfspunkt |
|---|---|
| Alltägliche Verarbeitung | Häufig speichern und das restliche Delta beim Beenden verringern |
| Beendigungsbenachrichtigungen der GUI | Auf die Abfrage grundsätzlich sofort TRUE zurückgeben. Idempotentes Speichern vom Aufräumen nach dem Commit trennen |
| Konsole und Dienste | Die Benachrichtigung nutzen, die zum App-Modell passt. Sich nicht nur auf ProcessExit von .NET oder das Verlängern der Schonfrist verlassen |
| Nicht unterbrechbare Arbeit | Begründung registrieren und Ablehnen nur so lange kombinieren, wie nötig. Auch für erzwungenes Herunterfahren vorsorgen |
| Speichern und Laden | Zusätzlich zu temporärer Datei, Leeren und Tausch eine Sicherung und Prüfung beim Start bereitstellen |
| Nach einem Neustart | Neustartregistrierung, Wiederherstellungsdaten und den Anmelde- oder Dienststartpfad prüfen |
Bei einem Herunterfahren mit aktiviertem Schnellstart können Kernel und Treiber aus dem Ruhezustand zurückkommen. Bei der Isolation von Störungen geben Sie „Neustart“ ausdrücklich an, und machen Sie die App so, dass sie unter vollständigem Herunterfahren und unter Hybrid funktioniert.5
flowchart TB
accTitle: Letzte Prüfung von der Beendigungsbehandlung bis zur Wiederherstellung
accDescr: Während der normalen Verarbeitung einen gespeicherten Zustand halten, bei einer Beendigungsbenachrichtigung klein aufräumen und die Daten prüfen, die auch ohne Benachrichtigung bleiben, um beim nächsten Start wiederherzustellen
daily["Alltäglich einen gespeicherten Zustand halten"] --> endq{"Gibt es eine Beendigungsbenachrichtigung?"}
endq -->|"Ja"| short["Mit kurzem Aufräumen beenden"]
endq -->|"Nein"| prior["Der letzte gespeicherte Zustand ist alles"]
short --> nextboot["Beim nächsten Start prüfen und wiederherstellen"]
prior --> nextboot
nextboot --> restart["Unter den erforderlichen Bedingungen zur Arbeit zurückkehren"]
Abbildung 20: Trennen Sie die Implementierung des Beendigungsereignisses nicht vom alltäglichen Speichern und der Wiederherstellung beim nächsten Start.
Zum Schluss versuchen Sie die Benachrichtigungspfade und die benötigte Zeit auf einer Prüfmaschine und bestätigen Sie auch die Wiederherstellung nach einem plötzlichen Stopp. In der nachträglichen Untersuchung nutzen Sie 1074 / 41 / 6008 als Hinweise, um eine geplante Beendigung von einer unerwarteten zu isolieren.
Wenn Sie das nächste Mal eine Funktion hinzufügen, fragen Sie „wenn mitten in dieser Verarbeitung eine Beendigungsbenachrichtigung kommt oder der Strom gezogen wird, was bleibt beim nächsten Start?“ Diese Antwort in den Entwurf aufzunehmen, ist die Vorsorge dagegen, Datenbeschädigung erst am nächsten Morgen zu entdecken.
Weiterführende Artikel
- Wie man eine exe oder DLL austauscht, die gerade verwendet wird — Restart Manager und das Problem „Datei wird verwendet“ beim automatischen Update
- Windows-Dienste erstellen und betreiben — Von der Abgrenzung zur Aufgabenplanung bis zur Umwandlung eines BackgroundService in einen Dienst
- Energiesparmodus, Ruhezustand, Modern Standby und lang laufende Anwendungen — Design gegen „über Nacht stehengeblieben“
- Die Tiefen von Windows-I/O (Teil 4) — Cache-Manager: Wann erreicht Ihr WriteFile tatsächlich die Festplatte?
- Design für Windows-Apps, das bei einem Absturz Protokolle und Dumps hinterlässt
- Eine Checkliste für den sicheren Umgang mit Kindprozessen in Windows-Apps
Zugehörige Beratungsfelder
KomuraSoft LLC übernimmt Entwurf und Implementierung von Gegenmaßnahmen gegen Herunterfahren und Stromausfall für Geräte-PCs 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 Wiederaufnahme. Es ist in Ordnung, vom Stadium „irgendetwas scheint bei jedem Herunterfahren kaputtzugehen, und ich weiß nicht, wo ich anfangen soll“ zu starten.
- Windows-Anwendungsentwicklung
- Fehleruntersuchung und Ursachenanalyse
- Technische Beratung und Design-Review
- Kontakt
Quellen
-
Microsoft Learn, WM_QUERYENDSESSION message. Dazu, dass WM_QUERYENDSESSION beim Sitzungsende gesendet wird und Apps TRUE zurückgeben sollten, um die Absicht des Benutzers zu respektieren (auch DefWindowProc gibt standardmäßig TRUE zurück); dazu, Aufräumen bis WM_ENDSESSION aufzuschieben; dazu, dass das System nach 5 Sekunden die UI der Apps anzeigt, die das Herunterfahren verhindern, sodass der Benutzer zwangsbeenden kann; zur Bedeutung der Bits ENDSESSION_LOGOFF/CLOSEAPP/CRITICAL in lParam; dazu, dass Herunterfahren und Neustart nicht zu unterscheiden sind; und dazu, Daten häufig zu speichern, um die Menge beim Beenden zu verringern. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, HandlerRoutine callback function. Zu den Ereignissen CTRL_C/BREAK/CLOSE/LOGOFF/SHUTDOWN, die ein mit SetConsoleCtrlHandler registrierter Handler empfängt; dazu, dass das Standardtimeout von CTRL_CLOSE_EVENT etwa 5000 Millisekunden und das von CTRL_SHUTDOWN_EVENT für Dienstprozesse etwa 20000 Millisekunden beträgt; dazu, dass CTRL_LOGOFF/SHUTDOWN_EVENT praktisch nur von Diensten empfangen werden, weil interaktive Apps bei der Abmeldung beendet werden; und dazu, dass der Handler auf einem getrennten Thread läuft. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Service Control Handler Function. Dazu, dass Dienste, die SERVICE_ACCEPT_PRESHUTDOWN erklären, zuerst SERVICE_CONTROL_PRESHUTDOWN empfangen, gefolgt von SERVICE_ACCEPT_SHUTDOWN-Diensten, die SERVICE_CONTROL_SHUTDOWN empfangen; dazu, dass die Standardschonfrist beim Herunterfahren etwa 20 Sekunden beträgt, mit WaitToKillServiceTimeout als Obergrenze bei einem OS-Neustart; dazu, diesen Wert nicht zu verlängern; dazu, dass der Steuerhandler innerhalb von 30 Sekunden zurückkehrt, STOP_PENDING mit einem Wartehinweis meldet und lange Arbeit einem anderen Thread überlässt; dazu, das Aufräumen mit Blick auf USV-Betrieb so schnell wie möglich zu beenden; und dazu, dass der SCM beim Herunterfahren standardmäßig keine Abhängigkeiten berücksichtigt. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, .NET runtime no longer provides default termination signal handlers. Dazu, dass die Laufzeit ab .NET 10 keine Standardhandler mehr für Windows CTRL_SHUTDOWN_EVENT/CTRL_CLOSE_EVENT (die Entsprechungen von SIGTERM/SIGHUP unter Unix) bereitstellt; dazu, dass die Standardbehandlung des Betriebssystems die App sofort beendet, sodass AppDomain.ProcessExit und AssemblyLoadContext.Unloading nicht mehr feuern; und dazu, dass zum App-Modell passende Signalbehandlung von übergeordneten Bibliotheken oder App-Code registriert werden sollte. ↩ ↩2 ↩3
-
Microsoft Learn, Fast startup causes hibernation or shutdown to fail in Windows 10 or Windows 8.1. Dazu, dass die Kernelsitzung bei Schnellstart nicht geschlossen, sondern in den Ruhezustand versetzt wird und Kernel- und Gerätetreiberzustand in hiberfil.sys gespeichert werden; dazu, dass „Neu starten“ immer einen vollständigen Start ausführt, weil ein vollständig neuer Windows-Zustand nötig ist; dazu, dass Schnellstart standardmäßig aktiviert ist und Deaktivieren nicht empfohlen wird; und dazu, dass Shutdown.exe standardmäßig ein vollständiges Herunterfahren ist und die Option /hybrid das Hybridverhalten ergibt. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Shutdown Changes for Windows Vista. Dazu, dass die Antwort auf WM_QUERYENDSESSION/WM_ENDSESSION jeweils um 5 Sekunden verzögert werden kann, wonach der Benutzer Fortsetzen oder Abbrechen wählt; dazu, dass Konsolen-Apps und Apps ohne sichtbares Fenster das Herunterfahren nicht abbrechen können und nach 5 Sekunden ohne Antwort oder bei einer FALSE-Antwort automatisch beendet werden; dazu, bei Bedarf eines Blocks eine Begründung mit ShutdownBlockReasonCreate zu registrieren; und dazu, dass Apps sich nicht darauf verlassen dürfen, das Herunterfahren blockieren zu können. ↩ ↩2 ↩3
-
Microsoft Learn, ShutdownBlockReasonCreate function (winuser.h). Dazu, sie am Beginn nicht unterbrechbarer Arbeit aufzurufen, um eine Begründungszeichenfolge zu registrieren, und ShutdownBlockReasonDestroy bei Abschluss aufzurufen; dazu, dass sie nur vom Thread aufrufbar ist, der das Fenster erzeugt hat; und dazu, die Zeichenfolge kurz und klar zu halten, weil der Benutzer die Begründung nur wenige Sekunden liest. ↩ ↩2 ↩3
-
Microsoft Learn, SetConsoleCtrlHandler function. Dazu, dass ein Prozess, der gdi32.dll oder user32.dll geladen hat, als Windows-App behandelt wird, deren Handler für CTRL_LOGOFF_EVENT/CTRL_SHUTDOWN_EVENT nicht aufgerufen werden; zur Umgehung, ein verstecktes Fenster zu erzeugen und WM_QUERYENDSESSION/WM_ENDSESSION zu behandeln; und dazu, dass Konsolenfunktionen während der Signalbehandlung möglicherweise nicht normal funktionieren. ↩
-
Microsoft Learn, SERVICE_PRESHUTDOWN_INFO structure (winsvc.h). Dazu, dass der SCM nach der PRESHUTDOWN-Benachrichtigung wartet, bis der Dienst stoppt oder das Timeout abläuft; dazu, dass das Standardtimeout 10 Sekunden ab Windows 10 Creators Update (Build 15063) und 3 Minuten davor beträgt; zur Konfiguration mit ChangeServiceConfig2; und dazu, dass Statusaktualisierungen während SERVICE_STOP_PENDING weiterlaufen können. ↩ ↩2
-
Microsoft Learn, RegisterApplicationRestart function (winbase.h). Zur Registrierung für den Neustart in den Szenarien Absturz, keine Rückmeldung, Update und updategetriebener Computerneustart; zur Angabe von Kommandozeilenargumenten für den Neustart; dazu, vor einem Problem zu registrieren, wobei die Behandlung von WM_QUERYENDSESSION im Update-Szenario die letzte Gelegenheit ist; dazu, dass Prozesse unter 60 Sekunden Laufzeit nicht neu gestartet werden; dazu, dass Neustart nach Absturz oder Hänger die Zustimmung des Benutzers braucht; und dazu, dass das Überschreiten eines OS-Neustarts ein Herunterfahren mit EWX_RESTARTAPPS/SHUTDOWN_RESTARTAPPS erfordert. ↩ ↩2
-
Microsoft Learn, Winlogon automatic restart sign-on (ARSO). Dazu, dass Windows Update die Anmeldeinformationen des letzten interaktiven Benutzers speichert und Autologon konfiguriert, wenn es einen automatischen Neustart beginnt; dazu, dass der Benutzer nach dem Neustart automatisch angemeldet und die Sitzung gesperrt wird; dazu, dass die gespeicherten Anmeldeinformationen nach einer erfolgreichen Anmeldung gelöscht werden; und zur Konfiguration über Gruppenrichtlinie (DisableAutomaticRestartSignOn und andere). ↩ ↩2
-
Microsoft Learn, ReplaceFileW function (winbase.h). Dazu, dass ReplaceFile die mehreren Schritte, die dem Speichern in eine neue Datei, dem vorübergehenden Umbenennen des Originals, dem Umbenennen der neuen Datei und dem Löschen des Originals entsprechen, in eine Funktion bündelt; dazu, dass es Attribute der Originaldatei wie Erstellungszeit, DACL, Verschlüsselung, Komprimierung und benannte Datenströme erhält; und dazu, dass Sicherung, zu ersetzende Datei und Ersatzdatei auf demselben Volume liegen müssen. ↩ ↩2
-
Microsoft Learn, File Caching. Dazu, dass Schreiben standardmäßig in den Systemcache gehen und durch verzögertes Schreiben auf den Datenträger gebracht werden; dazu, dass FILE_FLAG_WRITE_THROUGH Daten sofort auf den Datenträger schreibt; dazu, dass FlushFileBuffers ausdrücklich leert; und dazu, dass Dateisystemmetadaten immer zwischengespeichert werden, das Bestätigen von Metadaten also ein Leeren oder Write-Through erfordert. ↩ ↩2
-
Microsoft Learn, PBT_APMPOWERSTATUSCHANGE event. Dazu, dass dieses Ereignis über WM_POWERBROADCAST geliefert wird, wenn zwischen Akku und Netzstrom gewechselt wird oder die Restladung sinkt; und dazu, nach dem Empfang GetSystemPowerStatus aufzurufen, um ACLineStatus, BatteryFlag, BatteryLifePercent und andere Member von SYSTEM_POWER_STATUS zu prüfen. ↩ ↩2
-
Microsoft Learn, Troubleshoot unexpected reboots using system event logs. Dazu, dass ein normaler Neustart Ereignis-ID 1074 aufzeichnet (welcher Prozess das Herunterfahren für wen und aus welchem Grund ausgelöst hat); dazu, dass ein unerwarteter Neustart Ereignis-ID 41 (Kernel-Power) und 6008 (das vorherige Herunterfahren war unerwartet) aufzeichnet; und dazu, diese IDs zu nutzen, um die Art des Neustarts zu isolieren. ↩ ↩2
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
Was Schnellstart wirklich tut — Warum „Herunterfahren“ unter Windows nicht dasselbe ist wie ein Neustart
Ein Windows-Herunterfahren ist standardmäßig ein Hybrid-Herunterfahren und speichert Kernel und Treiber in hiberfil.sys. Warum nur ein Ne...
Das Netzwerk läuft, aber Windows sagt „Kein Internet“ — NCSI, DNS, Proxy und VPN unter Windows eingrenzen
Warum Windows „Kein Internet“ meldet, während das Netzwerk läuft — ausgehend vom NCSI-Urteil. DNS, Proxy, VPN und Anmeldeportale mit rein...
Time Travel Debugging — Langlaufende Fehler, die sich nicht reproduzieren, aufzeichnen und zurückspulen
Ein Fehler, der nur einmal im Monat auftritt, hinterlässt im Absturz-Dump nur das Ergebnis. Mit Time Travel Debugging (TTD) in WinDbg zei...
Warum Argumente zerbrechen — Die Regeln der Windows-Kommandozeilenargumente
Windows übergibt CreateProcess eine einzige Zeichenfolge, die der Empfänger zerlegt. Behandelt die Regeln von CommandLineToArgvW, CRT und...
Ende der Wartung von Windows-Druckertreibern — Wie Geschäftsanwendungen Bericht- und Etikettendruck vorbereiten
Microsoft stellt v3/v4-Druckertreiber schrittweise ein. Was Windows protected print mode entfernt und wie Geschäftsanwendungen Bericht- u...
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.
Windows-App-Entwicklung
Geschäftsanwendungen, Geräteintegration und Kommunikationstools von den Anforderungen bis zur Umsetzung.
Häufige Fragen
Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.
- Ein Problem, das nach „Herunterfahren“ nicht wegging, war nach „Neu starten“ verschwunden. Warum?
- Auf Client-Betriebssystemen ab Windows 8 nutzt „Herunterfahren“, wenn Schnellstart aktiviert ist (der Standard auf vielen PCs, die den Ruhezustand unterstützen), einen Mechanismus namens Hybrid-Herunterfahren. Der Benutzer wird abgemeldet, aber der Zustand von Kernel und Treibern wird in die Ruhezustandsdatei geschrieben und beim nächsten Start unverändert wiederhergestellt. Der Kern des Betriebssystems ist also nicht zurückgesetzt. „Neu starten“ führt dagegen immer einen vollständigen Start aus, sodass Störungen in Treibern und Diensten zurückgesetzt werden. Schreiben Sie in die Isolationsprozedur ausdrücklich „neu starten“, nicht „herunterfahren und wieder einschalten“. Wenn Sie von der Kommandozeile ein vollständiges Herunterfahren wollen, leistet das shutdown /s.
- Kann ich das Herunterfahren aufhalten, 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. Registrieren Sie mit ShutdownBlockReasonCreate eine Begründungszeichenfolge nur, solange eine nicht unterbrechbare Operation läuft, dann 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 unter Umständen gar nicht. Der richtige Weg ist daher nicht zu blockieren, sondern mit häufigem automatischem Speichern die gefährdeten Daten zu verringern und Aufräumen zu entwerfen, das innerhalb weniger Sekunden nach der Beendigungsbenachrichtigung fertig ist.
- Das Stoppen meines Windows-Dienstes dauert lange. Lässt sich 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; die Benachrichtigung kommt vor den anderen, 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 Stoppverarbeitung selbst so, dass sie in wenigen Sekunden fertig ist.
- Ist es in Ordnung, das Aufräumen beim Herunterfahren in AppDomain.ProcessExit von .NET zu machen?
- Verlassen Sie sich nicht darauf. Früher registrierte die Laufzeit Standard-Signalhandler, und das ProcessExit-Ereignis 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 Situationen nicht mehr. Implementieren Sie das Aufräumen auf dem Benachrichtigungspfad, der zum App-Modell passt: bei GUI-Apps FormClosing oder SessionEnding (das sind Benachrichtigungen der Abfragephase, beschränken Sie sie also auf idempotente Speicherungen und führen Sie Aufräumen, das erst nach dem Commit der Beendigung möglich ist, in einem WM_ENDSESSION-Hook aus); bei Generic Host / Worker Service IHostApplicationLifetime und StopAsync; bei Konsolen-Apps SetConsoleCtrlHandler oder PosixSignalRegistration.
- Wie verhindere ich, dass Dateien durch einen plötzlichen Stromausfall beschädigt werden?
- Ein Stromausfall bringt überhaupt keine Benachrichtigung, also bleibt nur, 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 normalen Betrieb können Sie dann die vollständige alte oder die vollständige neue Datei lesen, aber die Atomizität von ReplaceFile über einen Stromausfall hinweg ist spezifikationsseitig nicht garantiert. Behalten Sie deshalb eine Sicherung (das dritte Argument) und implementieren Sie als Satz eine Laderoutine, die die Primärdatei beim Start prüft und bei Beschädigung auf die Sicherung zurückfällt. Außerdem bedeutet Erfolg von WriteFile nicht, dass die Daten den Datenträger erreicht haben; an wichtigen Prüfpunkten bestätigen Sie das Schreiben mit FlushFileBuffers oder FILE_FLAG_WRITE_THROUGH. Auf Geräte-PCs gehört zur Standardausstattung, das mit einer USV zu kombinieren, den Wechsel auf Akkubetrieb 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.