L'arrêt de Windows vu depuis l'application — survivre correctement aux notifications de fermeture, aux redémarrages et aux coupures d'alimentation

· Mis à jour le: · · Windows, Arrêt, Développement Windows, Services Windows, PC d'équipement, Intégrité des données, Fonctionnement de longue durée, UPS

Historique des révisions (première version, publiée le 21 Aug 2026)
Première publication
Citer cet article(DOI (archive enregistrée): 10.5281/zenodo.22176527)

Les DOI ci-dessous renvoient à des versions déjà archivées et peuvent différer du texte actuel. Pour citer le texte actuel, utilisez l’URL de cette page.

Go Komura (2026). L'arrêt de Windows vu depuis l'application — survivre correctement aux notifications de fermeture, aux redémarrages et aux coupures d'alimentation. KomuraSoft LLC. https://comcomponent.com/fr/blog/windows-shutdown-handling-for-apps/

DOI (archive enregistrée)
10.5281/zenodo.22176527
DOI (dernière version enregistrée)
10.5281/zenodo.22176528

« Un redémarrage nocturne de Windows Update a corrompu le fichier dans lequel nous étions en train de mesurer. » « Se déconnecter d’un PC partagé efface ce que j’étais en train d’éditer. » Pour prévenir ce genre d’accident, l’arrêt ne doit pas être traité comme une exception : être prié de l’extérieur de se fermer doit être conçu comme un comportement normal de l’application.

Le code qui reçoit la notification de fermeture ne suffit pas à lui seul. Le temps disponible après la notification est court, et une coupure d’alimentation soudaine n’apporte aucune notification du tout. L’axe de cet article est de faire de l’enregistrement courant, d’un nettoyage court et de la récupération au démarrage suivant un tout continu.

À l’intention des responsables informatiques des PME et des développeurs d’applications Windows, en particulier ceux qui travaillent avec des PC d’équipement et des applications de longue durée, cet article ordonne le choix d’un chemin de notification, l’implémentation, la reprise après un redémarrage, la préparation à une coupure d’alimentation et la vérification. La base technique est constituée des sources primaires Microsoft Learn, à août 2026, auxquelles l’article original se référait.

1. D’abord la conclusion — réduire ce qu’il faut enregistrer avant d’attendre une notification

Maintenez l’application dans un état où elle peut se fermer en quelques secondes après une notification, et faites en sorte que le démarrage suivant puisse récupérer même lorsqu’aucune notification n’arrive. C’est la ligne de conduite de base pour le traitement de l’arrêt.

Plutôt que d’enregistrer un grand volume de données après la notification de fermeture, enregistrez à chaque jalon du travail et gardez le delta restant à la fermeture petit. Pour les applications graphiques et la fermeture de console, environ 5 secondes est le chiffre clé. Les services ont un délai de grâce différent, mais aucun n’est un temps que vous êtes assuré d’obtenir en entier.123

1.1. Choisir le chemin de notification de votre application

Forme de l’application Où la demande de fermeture arrive D’abord à bien faire Chapitre à lire
Application graphique Win32 WM_QUERYENDSESSION et WM_ENDSESSION Répondre à la requête par TRUE immédiatement, en principe. Faire le nettoyage après l’engagement de la fermeture dans WM_ENDSESSION Chapitre 3
WinForms / WPF FormClosing / SessionEnding, plus un hook de message si besoin Les deux événements appartiennent à la phase de requête. Déplacer le nettoyage qui ne peut pas être annulé vers la notification engagée Chapitre 3
Application console brute SetConsoleCtrlHandler et similaires La fermeture et la déconnexion/l’arrêt sont notifiés sous des conditions différentes Chapitre 5
Service Windows SHUTDOWN / PRESHUTDOWN depuis le SCM Déclarer le drapeau des contrôles acceptés et revenir tout de suite du gestionnaire de contrôle Chapitre 6
Generic Host / Worker Service IHostApplicationLifetime et StopAsync Concentrer le nettoyage sur le chemin d’arrêt de l’hôte et fixer ShutdownTimeout explicitement Chapitres 5 et 6

En .NET, ne vous fiez pas à AppDomain.ProcessExit seul. À partir de .NET 10, le runtime ne fournit plus de gestionnaire par défaut pour CTRL_CLOSE_EVENT / CTRL_SHUTDOWN_EVENT. Ce chemin par signal externe est distinct d’une sortie normale telle que le retour de Main. La section 5.2 en détaille le sujet.4

1.2. Deux préparations sont nécessaires en dehors du traitement des notifications

Quand la notification arrive, ne demandez pas à l’utilisateur « Voulez-vous enregistrer ? » ; terminez par un nettoyage court et fermez. Le blocage temporaire pour un travail vraiment ininterruptible est traité au chapitre 4, et la reprise automatique après un redémarrage au chapitre 7.

L’autre préparation est de laisser des données encore lisibles après une coupure d’alimentation qui n’apporte aucune notification. L’enregistrement dans un fichier temporaire, le vidage, l’échange, la conservation d’une sauvegarde et la validation au démarrage sont rassemblés au chapitre 8. Une fois le chemin de notification implémenté, poursuivez jusqu’à la conception d’enregistrement et de récupération du chapitre 8 et à la vérification du chapitre 9.

Une conception d'enregistrement commune aux notifications de fermeture et à la coupure d'alimentationL'enregistrement courant maintient le delta non enregistré petit ; avec une notification un nettoyage court suit, et sans elle les données enregistrées sont validées et récupérées avant la reprise du travailOuiNonEnregistrer à chaque jalonY a-t-il une notification à la fermeture ?N'enregistrer que le delta restant et fermerAucun nettoyage n'est possible sur placeValider et récupérer au démarrage suivantReprendre le travail

Figure 1 : plutôt que de s’acharner seulement à la notification, faites de l’enregistrement courant jusqu’au démarrage suivant un processus continu.

Dans le diagramme, un trait continu marque une relation qui vaut toujours et un trait pointillé une relation conditionnelle (les conditions figurent dans l’explication de chaque relation sur la page de détail). La liste complète des relations (14 au total, avec preuve et niveau de certitude) et les définitions des concepts principaux sont rassemblées sur la page de détail de la carte des connaissances (en japonais). Données : JSON-LD / Turtle

2. Distinguer d’abord — déconnexion, arrêt, redémarrage et coupure d’alimentation

2.1. Regarder la session utilisateur et le noyau séparément

Couper « ce qui se termine » en deux parties rend le traitement plus facile à organiser. La session utilisateur est le périmètre dans lequel tournent les applications de cet utilisateur. Le noyau et les pilotes, eux, appartiennent au côté OS et continuent de tourner après la déconnexion de l’utilisateur.

Opération Session utilisateur Noyau et pilotes Notifications
Déconnexion Se termine Continue de tourner L’interface graphique reçoit la requête de fermeture et la notification engagée. Les services ne s’arrêtent pas à la déconnexion
Arrêt avec démarrage rapide activé Se termine Enregistre son état de mise en veille prolongée dans hiberfil.sys Les notifications de fin de session de l’interface graphique et la notification d’arrêt aux services
Redémarrage Se termine Se termine complètement et fait un démarrage complet la fois suivante Comme ci-dessus
Coupure d’alimentation soudaine Perdue instantanément Perdus instantanément Aucune notification

Dans le WM_QUERYENDSESSION de l’interface graphique, le bit ENDSESSION_LOGOFF de lParam indique une déconnexion. Si lParam vaut 0, il s’agit d’un arrêt ou d’un redémarrage, et les deux ne se distinguent pas. Traitez lParam comme un masque de bits.1

Pour les données non enregistrées de l’application, la déconnexion et l’arrêt appellent la même préparation. Ne les séparez pas en « pas besoin d’enregistrer à la déconnexion » ; dirigez les deux vers une routine d’enregistrement commune. Cela ne rend pas identiques les conditions de notification des applications console et des services ; vérifiez les chemins des chapitres 5 et 6 pour chacun.

Penser la déconnexion et la sortie système séparémentLa déconnexion termine les applications de l'utilisateur tandis que les services continuent ; un arrêt ou un redémarrage du système arrête aussi les services ; une coupure d'alimentation n'en notifie aucunDéconnexionLes applications de l'utilisateur se fermentLes services continuent de tournerArrêt ou redémarrageLes services sont arrêtés aussiCoupure d'alimentationAucune notification ni à l'un ni à l'autre

Figure 2 : une déconnexion permet d’exercer le traitement de fermeture de l’interface graphique, mais elle ne compte pas comme test du traitement d’arrêt d’un service.

2.2. Pourquoi « l’arrêt ne le corrige pas, le redémarrage si »

Sur les OS clients à partir de Windows 8, le démarrage rapide est activé par défaut sur de nombreux PC qui prennent en charge la mise en veille prolongée. Dans cette configuration, un arrêt déconnecte l’utilisateur, mais l’état du noyau et des pilotes de périphérique est enregistré dans le fichier de mise en veille prolongée et restauré au démarrage suivant. Couper l’alimentation ne signifie pas nécessairement que tout l’état de l’OS a été réinitialisé.5

Ce comportement est conditionnel. Là où la mise en veille prolongée est désactivée (powercfg /hibernate off), là où le démarrage rapide est coupé par stratégie ou dans les Options d’alimentation, et sur Windows Server, vous obtenez l’arrêt complet traditionnel. Vérifiez les paramètres des Options d’alimentation, et utilisez powercfg /a pour vérifier si le démarrage rapide est disponible.

« Redémarrer », en revanche, exécute toujours un cycle de démarrage complet. Dans une procédure d’isolement d’un ennui de pilote, écrivez explicitement « redémarrer » plutôt que « éteindre et rallumer ».5

Démarrage rapide face à un démarrage completUn arrêt avec démarrage rapide activé enregistre et restaure l'état du noyau et des pilotes, tandis qu'un arrêt complet ou un redémarrage initialise tout par un démarrage completOuiNonArrêtUtiliser le démarrage rapide ?Mettre le noyau et les pilotes en veille prolongéeRestaurer l'état au démarrage suivantArrêt completInitialiser par un démarrage complet la fois suivanteRedémarrage

Figure 3 : couper l’alimentation n’est pas la même chose que réinitialiser le noyau et les pilotes.

Depuis la ligne de commande, shutdown /s demande un arrêt complet explicite et shutdown /s /hybrid le comportement hybride. Shutdown.exe a pour défaut un arrêt complet, il est donc important de ne pas supposer que c’est la même chose que l’élément « Arrêter » à l’écran.5

Désactiver le démarrage rapide comme contournement n’est pas recommandé. Construisez l’application pour qu’elle fonctionne qu’il soit activé ou désactivé. Par exemple, n’estimez pas le « temps de fonctionnement cumulé » d’un équipement à partir du seul instant de démarrage de l’OS ; tenez compte dans la conception du fait que l’état du noyau peut être reporté.

3. Applications graphiques — séparer la requête de la fermeture engagée

3.1. WM_QUERYENDSESSION est la question, WM_ENDSESSION est le résultat

Une application qui a une fenêtre et une file de messages est notifiée de la fin d’une session en deux étapes.1

Message Signification Quoi faire
WM_QUERYENDSESSION La requête « Peut-on fermer ? » Renvoyer TRUE immédiatement, en principe. DefWindowProc renvoie aussi TRUE par défaut
WM_ENDSESSION, wParam=TRUE La fin de la session est engagée Faire le nettoyage court : enregistrer, déconnecter, etc.
WM_ENDSESSION, wParam=FALSE La fin de la session a été annulée L’application continue de tourner. Ne pas faire le nettoyage qui n’est possible qu’après l’engagement de la fermeture

Renvoyer TRUE vous-même à la requête ne signifie pas que la fermeture est certaine. Une autre application peut refuser, et la fermeture est annulée. Si vous déconnectez ou cédez des ressources dont vous avez besoin à ce moment, une application qui n’a pas fermé ne peut plus travailler. C’est pourquoi le nettoyage est déplacé vers la notification engagée.1

La requête de l'interface graphique et le résultat de la fermetureMême après TRUE à WM_QUERYENDSESSION une autre application peut annuler la fermeture, donc le nettoyage post-engagement ne s'exécute que lorsque wParam de WM_ENDSESSION est TRUEOuiNonWM_QUERYENDSESSIONRenvoyer TRUE immédiatement, en principeWM_ENDSESSIONwParam est-il TRUE ?Nettoyage post-engagementFermeture annulée ; continuer

Figure 4 : ne confondez pas la réponse qui autorise la fermeture avec la notification que la fermeture est engagée.

Il y a des cas où vous pouvez renvoyer FALSE pour refuser la fermeture, mais la règle est de respecter l’intention de l’utilisateur de fermer. Une application qui refuse est affichée comme une application qui empêche l’arrêt. Les applications console et les applications sans fenêtre visible ont aussi des contraintes : dans une configuration ordinaire, une application qui ne répond pas en 5 secondes peut être terminée automatiquement. Traitez le blocage comme le traitement exceptionnel du chapitre 4 et ne l’utilisez pas pour l’enregistrement ordinaire.6

3.2. Environ 5 secondes n’est pas une garantie que vous pourrez finir d’enregistrer

Si vous retardez la réponse d’environ 5 secondes à l’étape WM_QUERYENDSESSION ou WM_ENDSESSION, le système affiche l’écran listant les applications qui empêchent l’arrêt, et l’utilisateur peut choisir de forcer l’arrêt. Après une terminaison forcée, il n’y a plus d’occasion de finir le reste de l’enregistrement.6

La contre-mesure est d’enregistrer couramment et de réduire le delta à la fermeture. Garez l’état de travail non enregistré dans un emplacement temporaire et restaurez-le au démarrage suivant. Ne concevez pas l’application pour afficher une boîte de confirmation pendant l’arrêt et attendre. Traitez la confirmation de fermeture ordinaire et une demande de fermeture venant de l’OS comme des choses distinctes.1

Garder le travail à la fermeture petitUne conception qui enregistre couramment laisse un petit delta à la fermeture, tandis qu'une conception qui accumule en mémoire jusqu'à la fermeture ne tient pas dans le court délai de grâce et risque une perte par terminaison forcéeEnregistrer courammentLe delta restant à la fermeture est petitFermer avec un nettoyage courtAccumuler en mémoire jusqu'à la fermetureTout enregistrer à la fermetureTrop peu de délai de grâce ; risque de terminaison forcée

Figure 5 : la préparation à fermer en quelques secondes se fait avant la notification de fermeture.

3.3. Dans WinForms et WPF, séparer l’enregistrement du nettoyage qui ne peut pas être annulé

Dans WinForms, le pendant est FormClosing avec CloseReason.WindowsShutDown ; dans WPF, c’est Application.SessionEnding. WPF peut aussi l’enregistrer via l’attribut SessionEnding en XAML, et on peut le traiter en redéfinissant OnSessionEnding.

Tous deux, toutefois, sont des événements de phase de requête. Ce qui est permis ici est au plus un « enregistrement de cliché idempotent » : inoffensif si la fermeture est annulée, et produisant le même résultat quel que soit le nombre d’exécutions. Le travail qui n’est possible qu’après l’engagement de la fermeture, comme déconnecter, se fait en recevant WM_ENDSESSION avec wParam=TRUE dans le WndProc de WinForms ou un hook WPF.

Répartition du traitement de fermeture dans WinForms et WPFFormClosing et SessionEnding enregistrent un cliché sûr même si la fermeture est annulée, et un hook sur WM_ENDSESSION avec TRUE exécute le nettoyage qui ne peut pas être annuléPhase de requêteFormClosingSessionEndingEnregistrement de cliché idempotentWM_ENDSESSION avec TRUERecevoir dans WndProc ou un hookTravail post-engagement tel que déconnecter

Figure 6 : n’entassez pas le travail post-engagement dans les événements du framework.

Les deux exemples suivants sont la partie qui n’enregistre que l’état de travail à l’étape de requête. Les extraits de code sont des extraits d’implémentation ; le contenu de la fonction d’enregistrement, le traitement des échecs, l’enregistrement des événements, etc. reviennent à l’application.

// WinForms : FormClosing est aussi appelé à l'arrêt et à la déconnexion
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
    if (e.CloseReason == CloseReason.WindowsShutDown)
    {
        // Ne faire qu'un enregistrement de cliché idempotent. N'afficher aucune boîte.
        // Ne pas non plus définir e.Cancel = true (refuser).
        SaveWorkingStateToTempFile();
        return;
    }

    // Dans les cas normaux, comme la fermeture par le bouton X, confirmer ici est possible
}
// WPF : App.xaml.cs
protected override void OnSessionEnding(SessionEndingCancelEventArgs e)
{
    base.OnSessionEnding(e);

    // ReasonSessionEnding.Logoff / Shutdown se distinguent, mais
    // l'approche de base est d'exécuter le même enregistrement de cliché pour les deux
    SaveWorkingStateToTempFile();

    // Ne pas définir e.Cancel = true sauf très bonne raison
}

Dans les deux cas, dirigez les données utilisées à la fermeture normale, à l’arrêt et, si possible, au plantage vers une fonction et un format d’enregistrement communs. Si vous évitez de créer un format distinct pour chaque chemin d’enregistrement, la logique de restauration au démarrage suivant peut aussi n’être qu’une. Concevoir pour laisser des informations au plantage est traité dans Concevoir la conservation des journaux et des dumps lors du crash d’une application Windows.

4. Bloquer temporairement, et seulement pour un travail qui ne peut pas être interrompu

4.1. Enregistrer un motif et refuser de fermer sont des travaux distincts

Un travail physiquement détruit s’il est interrompu, comme graver un CD ou écrire un micrologiciel, est l’exception. Enregistrez un motif avec ShutdownBlockReasonCreate au début du travail, et levez-le avec ShutdownBlockReasonDestroy à la fin. Le motif enregistré s’affiche sur l’écran listant les applications qui empêchent l’arrêt.7

Notez toutefois que enregistrer une chaîne de motif seul n’arrête pas l’arrêt. Combinez-la avec un drapeau « protégé » et, seulement tant qu’il est positionné, renvoyez FALSE à WM_QUERYENDSESSION. Quand le travail se termine, levez à la fois le motif et le drapeau protégé.

Répartition des rôles dans un blocage temporaire de fermetureSeulement pendant un travail ininterruptible, combiner l'enregistrement du motif et le refus de la requête, et lever les deux à la fin ; un arrêt forcé par l'utilisateur ne peut toujours pas être empêchéAnnulerForcerLe travail ininterruptible commenceEnregistrer le motif et positionner le drapeau protégéTraiter sur un workerTerminer ; lever le motif et le drapeauUne requête de fermeture arrive entre-tempsRenvoyer FALSE et afficher le motifDécision de l'utilisateurContinuerPeut être terminé

Figure 7 : enregistrer un motif n’est pas un refus, et même implémenter le refus n’empêche pas un arrêt forcé.

4.2. Garder le thread d’interface capable de recevoir la demande de fermeture

Enregistrez et levez le motif depuis le thread qui a créé la fenêtre cible. Les appels depuis d’autres threads échouent.7 Le long travail ininterruptible lui-même, en revanche, passe sur un thread worker. Si le thread d’interface est bloqué par un traitement synchrone, l’application devient « Ne répond pas » avant que le message nécessaire au refus soit jamais traité.

[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);

// Appeler depuis le thread qui a créé la fenêtre principale (les appels depuis d'autres threads échouent)
_criticalOperationInProgress = true;
ShutdownBlockReasonCreate(this.Handle, "Écriture des données de mesure dans un fichier");
try
{
    // Exécuter le travail ininterruptible sur un thread worker. L'exécuter de façon synchrone
    // sur le thread d'interface arrête la pompe à messages, et l'application est forcée
    // comme « Ne répond pas » avant que le code de refus de WM_QUERYENDSESSION ci-dessous puisse s'exécuter
    await Task.Run(() => WriteMeasurementData());
}
finally
{
    ShutdownBlockReasonDestroy(this.Handle);
    _criticalOperationInProgress = false;
}

// En plus, renvoyer FALSE à WM_QUERYENDSESSION seulement pendant la protection, pour refuser
protected override void WndProc(ref Message m)
{
    const int WM_QUERYENDSESSION = 0x0011;
    if (m.Msg == WM_QUERYENDSESSION && _criticalOperationInProgress)
    {
        m.Result = IntPtr.Zero;   // Refuser. La chaîne de motif enregistrée s'affiche dans l'interface plein écran
        return;
    }
    base.WndProc(ref m);
}

Cet exemple est le squelette qui combine l’enregistrement du motif, le traitement sur un worker et le refus de la requête. Le traitement d’erreur, y compris les échecs d’API, est nécessaire à part. Gardez la chaîne de motif courte et concrète, et limitez l’enregistrement au temps où le travail ne peut vraiment pas être interrompu plutôt que de le laisser enregistré toute la vie de l’application.7

Même ainsi, l’utilisateur peut choisir de forcer l’arrêt. Il existe aussi des chemins de fermeture forcée tels que ENDSESSION_CRITICAL, donc ne faites jamais de la capacité à bloquer une prémisse de l’intégrité des données. La préparation au cas où vous n’avez pas pu l’arrêter est la conception d’enregistrement et de récupération du chapitre 8.6

5. Console et .NET — vérifier les conditions sous lesquelles les notifications arrivent

5.1. Notifications et délais de grâce reçus par SetConsoleCtrlHandler

Dans une application console, les signaux de contrôle arrivent au gestionnaire enregistré avec SetConsoleCtrlHandler. Contrairement au traitement des messages de l’interface graphique, le gestionnaire s’exécute sur un thread distinct.2

Signal Quand il se produit Délai de grâce par défaut
CTRL_C_EVENT / CTRL_BREAK_EVENT Ctrl+C / Ctrl+Break Pas de délai d’expiration explicite
CTRL_CLOSE_EVENT Fermeture de la console, « Fin de tâche » dans le Gestionnaire des tâches, etc. Environ 5 secondes
CTRL_SHUTDOWN_EVENT Processus de service à l’arrêt du système Environ 20 secondes

Terminer de force un processus depuis l’onglet « Détails » du Gestionnaire des tâches, par exemple, est une terminaison immédiate sans notification, et sort de cette table.2

Ne concevez pas une application console en session interactive pour attendre CTRL_LOGOFF_EVENT ou CTRL_SHUTDOWN_EVENT. Les applications interactives sont terminées au moment de la déconnexion, donc en pratique seuls les processus qui tournent comme services peuvent les recevoir. De plus, un processus qui charge gdi32.dll ou user32.dll est traité comme une application Windows, et les gestionnaires LOGOFF/SHUTDOWN ne sont pas appelés. Le contournement officiel dans ce cas est de créer une fenêtre cachée et de recevoir WM_QUERYENDSESSION / WM_ENDSESSION.28

Conditions pour recevoir les notifications de fermeture de la consoleUne console interactive peut recevoir la notification de fermeture mais ne peut pas compter sur les notifications de déconnexion ou d'arrêt, et même un service a besoin d'un autre chemin de notification une fois qu'il a chargé les DLL d'interfaceSession interactiveServiceNonOuiProcessus consoleLa fermeture va au gestionnaire de contrôleAttendre LOGOFF ou SHUTDOWN ?On ne peut pas compter sur cette notificationDLL d'interface chargées ?Traiter le signal de contrôle concernéRecevoir par une fenêtre cachée

Figure 8 : ne traitez pas la prise en charge de la fermeture de console comme une prise en charge de l’arrêt.

L’extrait suivant prépare Ctrl+C et la fermeture de console. Il conserve le délégué pour empêcher le GC de le collecter, et après un nettoyage court poursuit vers le gestionnaire par défaut. Enregistrer ceci seul ne garantit pas les notifications d’arrêt pour une application interactive.

// Application console : nettoyer sur Ctrl+C et fermeture de console
[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;  // Garder une référence pour que le GC ne le collecte pas

static bool OnCtrlEvent(int ctrlType)
{
    // Ne faire que le nettoyage qui se termine en 5 secondes
    FlushAndCloseDataFile();
    return false;   // Poursuivre vers le gestionnaire par défaut ; le processus se termine
}

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

5.2. À partir de .NET 10, ne pas placer le nettoyage uniquement dans ProcessExit

À partir de .NET 10, le runtime ne fournit plus de gestionnaire par défaut pour CTRL_CLOSE_EVENT / CTRL_SHUTDOWN_EVENT. Sans gestionnaire à vous ou d’une bibliothèque de niveau supérieur, le traitement par défaut de l’OS termine le processus, et sur ce chemin ni AppDomain.ProcessExit ni AssemblyLoadContext.Unloading ne se déclenchent.4

C’est un changement du comportement par défaut pour les signaux de terminaison externes. Cela ne signifie pas que ProcessExit ne se déclenche plus à une sortie normale telle que le retour de Main.

Retirer la dépendance au gestionnaire de terminaison par défaut de .NETL'ancien runtime reliait les signaux concernés à ProcessExit, mais à partir de .NET 10 aucun gestionnaire par défaut n'est fourni, donc utiliser le chemin de notification du modèle d'applicationNonOuiSignal CLOSE ou SHUTDOWNTraitement par défaut de l'ancien runtimeDéclenche ProcessExit et assimilésPas de traitement par défaut à partir de .NET 10L'application le traite-t-elle ?Terminé par le traitement par défaut de l'OSNettoyage qui correspond au modèle

Figure 9 : distinguez une sortie normale d’une terminaison par signal externe, et fournissez le gestionnaire dont votre modèle d’application a besoin.

Concentrez le nettoyage là où il correspond au modèle d’application. Pour une interface graphique, ce sont les événements et la notification engagée du chapitre 3 ; pour Generic Host, IHostApplicationLifetime et BackgroundService.StopAsync ; pour une application console brute, SetConsoleCtrlHandler ou PosixSignalRegistration. En utilisant ce dernier, choisissez les signaux qui correspondent aux chemins de terminaison visés, tels que SIGINT, SIGTERM et SIGHUP.4

Dans Generic Host, fixez HostOptions.ShutdownTimeout explicitement. Le paramètre côté hôte seul, toutefois, ne prolonge pas le délai de grâce extérieur de l’interface graphique, de la console ou du SCM. Même si Ctrl+C n’a pas de délai d’expiration explicite, il faut encore se préparer à une coupure d’alimentation et à d’autres chemins de terminaison. Dans chaque modèle la ligne de conduite est la même : garder l’état enregistré et garder le travail après la notification petit.

Le traitement d'arrêt de l'hôte et le délai de grâce de fermeture extérieurL'arrêt de Generic Host est concentré dans StopAsync, mais fixer le délai HostOptions ne prolonge pas le délai de grâce de fermeture de l'OS ou du SCM, donc vérifier les deuxDemande de fermeture de l'OS ou du SCMChemin d'arrêt de l'hôteNettoyer dans StopAsyncFixer le temps d'arrêt côté hôteLe délai de grâce extérieur existe à partMesurer s'il se termine rapidement

Figure 10 : vérifiez séparément les limites de temps côté hôte et côté OS, et ne vous contentez pas de prolonger le paramètre.

6. Services Windows — revenir immédiatement du gestionnaire de contrôle

6.1. La différence entre SHUTDOWN et PRESHUTDOWN

Les services ne s’arrêtent pas à la déconnexion, mais ils sont arrêtés à l’arrêt et au redémarrage. La notification vient du SCM (Gestionnaire de contrôle des services), et le service doit déclarer le drapeau qui correspond au code de contrôle qu’il veut recevoir.3

Drapeau à déclarer Code de contrôle livré Quand l’utiliser
SERVICE_ACCEPT_SHUTDOWN SERVICE_CONTROL_SHUTDOWN La notification d’arrêt ordinaire. Le délai de grâce par défaut est d’environ 20 secondes et dépend de WaitToKillServiceTimeout
SERVICE_ACCEPT_PRESHUTDOWN SERVICE_CONTROL_PRESHUTDOWN Livré avant le SHUTDOWN ordinaire. Le SCM attend que le service s’arrête ou que le délai configuré expire
Étapes de la notification de fermeture aux servicesLe SCM notifie d'abord les services qui acceptent PRESHUTDOWN, attend qu'ils s'arrêtent ou expirent, puis poursuit vers la notification SHUTDOWN ordinaireL'arrêt du système commenceNotifier les services qui acceptent PRESHUTDOWNAttendre l'arrêt ou l'échéance configuréeNotifier les services qui acceptent SHUTDOWNAprès le délai de grâce, poursuivre vers la sortie système

Figure 11 : être notifié tôt et être attendu sont des conditions distinctes ; PRESHUTDOWN a aussi une échéance configurée.

Le délai PRESHUTDOWN se configure avec ChangeServiceConfig2(SERVICE_CONFIG_PRESHUTDOWN_INFO). Le défaut est 10 secondes à partir de Windows 10 Creators Update (build 15063) et 3 minutes avant. Si vous supposez « PRESHUTDOWN achète toujours 3 minutes », vous n’obtiendrez pas le temps que vous attendiez.9

Parce que PRESHUTDOWN retient l’arrêt de tout le système, utilisez-le seulement quand c’est vraiment nécessaire. Réécrire le WaitToKillServiceTimeout ordinaire depuis le service pour le prolonger n’est pas recommandé non plus.3

6.2. Séparer la réception de la notification de l’arrêt réel

Le gestionnaire de contrôle doit revenir en 30 secondes, mais plutôt que de penser à cela comme 30 secondes que vous pouvez utiliser, structurez-le pour signaler l’arrêt et revenir immédiatement. Confiez le long travail à un autre thread et signalez SERVICE_STOP_PENDING.3

// Service Win32 : accepter PRESHUTDOWN et laisser le travail d'arrêt à un 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);   // Signaler au worker de s'arrêter et revenir immédiatement
        return NO_ERROR;
    }
    return ERROR_CALL_NOT_IMPLEMENTED;
}

// Côté worker : si le nettoyage dure plus longtemps que l'indication d'attente, continuer
// à signaler SERVICE_STOP_PENDING périodiquement en incrémentant dwCheckPoint. Le SCM juge
// d'après l'indication d'attente et le point de contrôle qui avance que le service est
// « encore vivant et avance ». Si les rapports s'arrêtent, il est traité comme bloqué et
// l'arrêt peut continuer. Toujours signaler SERVICE_STOPPED une fois terminé

Côté worker, si le nettoyage dure plus longtemps que l’indication d’attente, continuez à signaler SERVICE_STOP_PENDING en avançant dwCheckPoint. Si les rapports de progression s’arrêtent, le service peut être jugé bloqué. Signaler SERVICE_STOPPED à l’achèvement fait partie du traitement d’arrêt.39

Répartition du travail entre le gestionnaire de contrôle du service et le workerLe gestionnaire de contrôle signale l'arrêt en attente, signale le worker et revient tout de suite ; le worker fait le nettoyage et les rapports de progression et signale enfin l'arrêtGestionnaire de contrôleSignaler STOP_PENDINGSignaler au worker de s'arrêterLe gestionnaire revient immédiatementLe worker nettoieSignaler la progression s'il dureSignaler STOPPED à l'achèvement

Figure 12 : ne bloquez pas le thread qui reçoit la notification par un long traitement d’arrêt.

6.3. Pouvoir finir rapidement même si une dépendance s’arrête d’abord

À l’arrêt, le SCM envoie par défaut les notifications sans égard aux dépendances de services. Le traitement d’arrêt doit gérer le cas où un service de dépendance est déjà indisponible, et ne doit pas trop attendre les réponses des pairs réseau. Plutôt que de passer du temps à libérer de la mémoire et assimilé, donnez la priorité à confirmer rapidement les données nécessaires.3

Cette ligne de conduite compte aussi par rapport à un onduleur. Plus un service prolonge son attente, plus il devient difficile de terminer l’arrêt de tout l’OS avant que la batterie s’épuise. Au lieu d’augmenter le délai de grâce, réduisez le travail à l’arrêt en enregistrant à chaque jalon.3

Lorsqu’un Worker Service .NET tourne sous UseWindowsService, STOP / SHUTDOWN sont convertis en un arrêt de l’hôte, qui mène à BackgroundService.StopAsync. L’implémentation standard, au moment de la rédaction de l’article original, n’accepte pas PRESHUTDOWN, donc si vous en avez besoin il faut étendre l’implémentation du gestionnaire. La ligne de conduite de fixer HostOptions.ShutdownTimeout explicitement et de terminer StopAsync lui-même en quelques secondes est la même. Pour l’implémentation d’ensemble, voir Comment créer et exploiter un service Windows.

7. Reprise après un redémarrage — aligner l’enregistrement, les données de restauration et l’ouverture de session

7.1. RegisterApplicationRestart préenregistre le chemin de reprise

Sur les PC d’équipement et en fonctionnement sans surveillance, la conception va au-delà d’enregistrer et de fermer : elle couvre la reprise du travail après le redémarrage. RegisterApplicationRestart est l’API qui enregistre l’application pour le redémarrage dans chacune de ces situations : un plantage (une exception non gérée), « Ne répond pas », un redémarrage d’application causé par une mise à jour, et un redémarrage de l’OS causé par une mise à jour.10

Vous pouvez indiquer des arguments de ligne de commande pour le redémarrage, tels que les fichiers qui étaient ouverts ou un point de restauration. S’enregistrer seul, toutefois, ne signifie pas une reprise automatique depuis chaque situation.

Point à vérifier Condition ou contrainte
Quand s’enregistrer Avant qu’un problème se produise. Dans le scénario de mise à jour, pendant le traitement de WM_QUERYENDSESSION est la dernière occasion
Prévention d’une boucle de redémarrage Un processus qui tourne depuis moins de 60 secondes n’est pas redémarré
Plantage ou blocage Redémarré après le consentement de l’utilisateur. Un redémarrage causé par une mise à jour est automatique
À cheval sur un redémarrage de l’OS Le côté initiateur doit appeler l’API d’arrêt avec EWX_RESTARTAPPS / SHUTDOWN_RESTARTAPPS
Processus élevé Non éligible au redémarrage automatique, donc un chemin de lancement explicite distinct est nécessaire

Pour une application qui a besoin d’élévation, concevez le chemin de reprise en faisant tourner l’interface aux privilèges standard et en séparant le travail privilégié dans un service, ou en utilisant une tâche du Planificateur de tâches réglée sur « Exécuter avec les privilèges les plus élevés » ou similaire.10

7.2. Le rappel de récupération et ARSO jouent des rôles distincts

Si vous utilisez aussi RegisterApplicationRecoveryCallback, WER (Rapport d’erreurs Windows) appelle le rappel de récupération au plantage et vous pouvez enregistrer les données en cours de travail. Pendant que l’enregistrement continue, appelez ApplicationRecoveryInProgress dans l’intervalle de ping enregistré, et notifiez ApplicationRecoveryFinished à l’achèvement. Si les notifications de progression s’arrêtent, le traitement de récupération peut être coupé.

Enregistrement et notification de progression dans le rappel de récupérationLe rappel de récupération invoqué par WER continue d'appeler ApplicationRecoveryInProgress dans l'intervalle de ping pendant l'enregistrement, et notifie ApplicationRecoveryFinished à l'achèvementPas encoreTerminéPréenregistrer le rappel de récupérationPlantage ; WER l'invoqueEnregistrer les données en cours de travailSignaler la progression dans l'intervalle de pingEnregistrement terminé ?Notifier la fin de la récupérationPeut être coupé si les rapports s'arrêtent

Figure 13 : ne vous contentez pas d’enregistrer ; notifiez WER de la progression et de l’achèvement.

Remplacer les fichiers en cours d’utilisation pendant une mise à jour et redémarrer est le travail de Restart Manager. Il est traité dans Comment remplacer un exe/DLL en cours d’utilisation.

Le mécanisme qui ramène la session utilisateur après un redémarrage de l’OS, lui, est ARSO (ouverture de session automatique au redémarrage de Winlogon). Lorsque Windows Update démarre un redémarrage automatique, il enregistre de façon sûre les informations d’identification du dernier utilisateur interactif et configure Autologon, et après le redémarrage il ouvre la session de cet utilisateur et verrouille l’écran.11

Enregistrement au redémarrage, ouverture de session et restauration des donnéesFace à un enregistrement préalable au redémarrage, un plantage exige le consentement de l'utilisateur, et les applications utilisateur après un redémarrage de l'OS ont besoin de la session restaurée par ARSO ou similaire et de l'état enregistré restauréS'enregistrer au redémarrage avant qu'un problème se produisePlantage ou Ne répond pasObtenir le consentement de l'utilisateurRedémarrer l'applicationRedémarrage de l'OS avec les drapeaux requisRestaurer la session par ARSO ou similaireLire le point de restauration enregistréVérifier la stratégie et les conditions de démarrage

Figure 14 : fournissez non seulement l’enregistrement au redémarrage de l’application, mais aussi le chemin par lequel la session et l’état de travail reviennent.

shutdown /g est la commande qui demande un redémarrage plus la reprise des applications enregistrées. ARSO peut être désactivé par une stratégie d’organisation telle que DisableAutomaticRestartSignOn, donc vérifiez-le avec les exigences de la reprise sans surveillance. Un traitement d’arrière-plan toujours nécessaire tourne mieux comme service Windows que s’il dépend de l’ouverture de session automatique de l’utilisateur.11

8. Coupure d’alimentation sans notification — concevoir l’enregistrement et le chargement comme une paire

8.1. Fichier temporaire, échange, sauvegarde et validation au démarrage comme un ensemble

Un disjoncteur déclenché, un bloc d’alimentation en panne ou une prise arrachée n’apporte ni WM_ENDSESSION ni PRESHUTDOWN. Si vous écrasez le fichier original sur place, une interruption en cours de route peut laisser un fichier où anciens et nouveaux contenus sont mélangés.

La base est d’écrire entièrement dans un fichier temporaire sur le même volume, de le vider, puis de l’échanger. ReplaceFile regroupe les étapes qui correspondent à enregistrer dans un nouveau fichier, mettre l’original de côté, renommer et supprimer, et il reprend des attributs tels que l’heure de création, la liste de contrôle d’accès et les flux de données alternatifs. Le fichier remplacé, le fichier de remplacement et la sauvegarde doivent être sur le même volume. File.Replace de .NET appelle cette API.12

Enregistrer un fichier et récupérer au démarrage suivantÉcrire dans un fichier temporaire sur le même volume, vider, échanger en conservant une sauvegarde, puis valider le fichier principal au démarrage et retomber sur la sauvegarde si besoinOuiNonFichier temporaire sur le même volumeÉcrire jusqu'au bout et viderÉchanger ; l'ancien contenu va dans .bakDémarrage suivantLe fichier principal est-il intact ?Lire le fichier principalRetomber sur la sauvegarde

Figure 15 : implémentez non seulement la façon sûre d’écrire, mais aussi la façon de lire lorsque le fichier est cassé.

// Le motif standard pour enregistrer réglages et données : écrire dans un fichier temporaire, puis échanger, en conservant l'ancien contenu
public static void SaveAtomically(string path, string content)
{
    string dir = Path.GetDirectoryName(path)!;
    string tmp = Path.Combine(dir, Path.GetRandomFileName());  // Le créer sur le même 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);   // Équivalent de FlushFileBuffers. Écrit les tampons de l'OS
                                           // vers le disque (les limites du cache côté périphérique sont à la section 8.2)
        }

        if (File.Exists(path))
            File.Replace(tmp, path, path + ".bak");  // Appelle ReplaceFile. Conserve l'ancien contenu en .bak
        else
            File.Move(tmp, path);
    }
    catch
    {
        // En cas d'échec en cours de route, ne pas laisser le fichier temporaire. Si les
        // enregistrements périodiques continuent d'échouer, des copies complètes finiraient par remplir le volume
        try { File.Delete(tmp); } catch { /* Un échec de suppression cède à l'exception d'origine */ }
        throw;
    }
}

Même si cet exemple s’appelle SaveAtomically, il ne garantit pas l’atomicité à travers une coupure d’alimentation. En fonctionnement normal, il laisse lisible soit l’ancien fichier complet, soit le nouveau fichier complet, mais ReplaceFile est une opération d’espace de noms en plusieurs étapes, et son atomicité face à une coupure d’alimentation soudaine n’est pas garantie par la spécification. C’est précisément pourquoi vous conservez le .bak et implémentez la routine de chargement qui valide le fichier principal au démarrage et retombe sur la sauvegarde s’il est cassé.12

Le catch de l’exemple existe pour que les fichiers temporaires ne s’accumulent pas sur les échecs ordinaires et ne remplissent pas le volume. Il n’attend pas que ce code s’exécute au moment d’une coupure d’alimentation. Pour les journaux et CSV en ajout seulement, n’appliquez pas l’échange du fichier entier tel quel ; utilisez un format qui tient compte de la façon dont il casse, par exemple « écrire une ligne par enregistrement et jeter une dernière ligne corrompue au chargement ».

8.2. Distinguer le succès de WriteFile de l’arrivée sur le disque

Même lorsque WriteFile réussit, les données peuvent encore se trouver dans le cache de l’OS. Windows écrit normalement dans les tampons système et applique les données au disque par écriture différée. Aux points de contrôle importants, videz avec FlushFileBuffers, ou spécifiez FILE_FLAG_WRITE_THROUGH à CreateFile pour demander une écriture immédiate. Les métadonnées du système de fichiers sont aussi mises en cache, donc les confirmer implique un vidage ou un write-through.13

Le write-through, toutefois, n’est pas la même chose que « ne pas utiliser le cache de l’OS ». Les appels fréquents à FlushFileBuffers sont inefficaces, donc envisagez de le combiner avec FILE_FLAG_NO_BUFFERING si besoin. En pratique, la conception réaliste est de vider aux points qui comptent pour l’intégrité, tels que les frontières de transaction et juste avant de fermer le fichier.13

La frontière entre le succès d'écriture et la persistanceUne écriture ordinaire atteint le stockage depuis le cache de l'OS avec un délai ; un vidage ou un write-through pousse à confirmer, mais les contraintes du cache côté périphérique restentWriteFile réussitPeut être dans le cache de l'OSÉcriture différéeVider à un point de contrôleAppliqué au stockageLimites du cache côté périphérique

Figure 16 : ne traitez pas le succès d’API, la confirmation du cache de l’OS et la résistance à une coupure d’alimentation comme la même chose.

Le cache volatil côté périphérique a aussi ses limites, et on ne peut pas dire « nous avons vidé, donc cela survit pleinement à une coupure d’alimentation sur n’importe quel matériel ». La relation entre le gestionnaire de cache, l’écriture différée et les caches matériels est expliquée en détail dans Les profondeurs de l’E/S Windows (partie 4) — Le gestionnaire de cache : quand votre WriteFile atteint-il vraiment le disque ?.

8.3. Utiliser l’onduleur pour transformer une coupure d’alimentation en arrêt planifié

Le rôle d’un onduleur n’est pas d’éliminer les pannes mais de transformer une coupure d’alimentation sans notification en un arrêt planifié avec notification. La condition dont vous avez besoin est ce rapport :

Autonomie de la batterie de l’onduleur > temps pour détecter le basculement + nettoyage des applications et des services + achèvement de l’arrêt de l’OS

Le basculement entre l’alimentation secteur et la batterie, et une charge restante faible, sont signalés par PBT_APMPOWERSTATUSCHANGE. Une interface graphique le reçoit par WM_POWERBROADCAST ; un service déclare SERVICE_ACCEPT_POWEREVENT puis reçoit SERVICE_CONTROL_POWEREVENT dans HandlerEx. WM_POWERBROADCAST n’est pas livré au gestionnaire de contrôle d’un service.14

Après l’avoir reçu, vérifiez ACLineStatus et BatteryLifePercent avec GetSystemPowerStatus, et poursuivez vers la suspension de la mesure, l’enregistrement et la demande d’arrêt.14

De la détection de l'onduleur à l'achèvement de l'arrêtDétecter le basculement de l'onduleur sur batterie par le chemin de notification approprié, vérifier l'état d'alimentation, poursuivre vers l'enregistrement et l'arrêt, et concevoir toute la séquence pour tenir dans l'autonomiePanne ; l'onduleur bascule sur batterieNotification d'alimentation sur le chemin correspondantVérifier l'état d'alimentation et la charge restanteSuspendre la mesure et enregistrerDemander un arrêt à l'OSTerminer le nettoyage et la sortie de l'OSFaire tenir toute la séquence dans l'autonomie

Figure 17 : faites tenir non seulement l’application, mais le temps jusqu’à la sortie de l’OS, dans l’autonomie de l’onduleur.

Dans les configurations où Windows voit l’onduleur comme une batterie, comme un onduleur USB typique, les API standard peuvent le surveiller. Si le logiciel de gestion du fournisseur a une fonction « arrêter à N % restants », vérifiez aussi que son seuil est cohérent avec le temps de nettoyage. La reprise depuis la veille ou la mise en veille prolongée est un sujet distinct ; voir Veille, mise en veille prolongée, Modern Standby et applications longue durée.

9. Vérification — confirmer les notifications, le timing et le résultat de récupération

9.1. Reproduire les chemins de fermeture sur une machine de test, pas en production

N’essayez pas d’abord sur le PC d’équipement de production ; utilisez une machine de test ou une machine virtuelle telle qu’Hyper-V. Sur une machine virtuelle, prenez un point de contrôle au préalable et répétez les tests avec des données de test que vous pouvez vous permettre de perdre.

Opération à essayer Quoi confirmer
Déconnexion Le WM_QUERYENDSESSION → WM_ENDSESSION de l’interface graphique et le traitement d’enregistrement. Pas un substitut pour vérifier l’arrêt d’un service
shutdown /s /t 0 Le comportement à un arrêt complet
shutdown /s /hybrid /t 0 Le comportement hybride dans une configuration qui utilise le démarrage rapide
shutdown /r /t 0 Un redémarrage avec un démarrage complet, et la reprise ensuite
Mise hors tension de la machine virtuelle Si le démarrage suivant récupère même lorsque l’OS invité s’arrête sans notification
Coupure d’alimentation sur du matériel équivalent à la production La résilience y compris le stockage physique et le contrôleur

Une déconnexion diffère en ce que le bit ENDSESSION_LOGOFF est positionné, mais c’est un moyen facile de confirmer le chemin de notification de l’interface graphique. Ne confondez pas les commandes d’arrêt complet, hybride et de redémarrage ; essayez-les séparément.15

Élargir la vérification de l'arrêt par étapesEssayer les notifications graphiques et chaque opération de fermeture dans l'environnement de test, confirmer la récupération après un arrêt soudain sur une machine virtuelle, puis vérifier la résilience à une coupure y compris le stockage sur du matériel équivalent à la productionPréparer la machine de test et les donnéesEssayer les chemins de notification et les opérations de fermetureMesurer le temps de nettoyageConfirmer la récupération après un arrêt soudain de la machine virtuelleVérifier sur du matériel réel y compris le stockageConfirmer les données et la reprise au démarrage suivant

Figure 18 : testez non seulement si l’application se ferme normalement, mais aussi ce qu’elle peut récupérer après un arrêt soudain.

Mettre hors tension une machine virtuelle ne reproduit que l’invité qui s’arrête sans préavis. Elle ne peut pas reproduire la perte du cache volatil d’un disque physique ni une corruption dépendante du contrôleur, donc lors d’une expédition comme PC d’équipement, faites la confirmation finale sur du matériel équivalent à la production.

Journalisez un horodatage au début et à la fin de la fonction de nettoyage, et mesurez si elle tient dans les environ 5 secondes pour une interface graphique et assimilés, ou dans le délai de grâce configuré pour le service. Confirmez non seulement que la fermeture a réussi, mais aussi ce qui a été chargé au démarrage suivant et d’où le travail a pu reprendre.

9.2. Isoler ce qui s’est passé pendant la nuit depuis le journal des événements

Dans le journal Système de Windows, l’ID d’événement 1074 enregistre le processus qui a initié l’arrêt, l’utilisateur et le motif. Pour un arrêt inattendu, 41 (Kernel-Power) ou 6008 est enregistré au démarrage suivant.15

# Vérifier l'historique récent des événements liés à l'arrêt
Get-WinEvent -FilterHashtable @{ LogName = 'System'; Id = 1074, 6008, 41 } -MaxEvents 20 |
    Select-Object TimeCreated, Id, ProviderName, Message |
    Format-List

Si 1074 montre un redémarrage de Windows Update et que les données étaient quand même corrompues, examinez d’abord le traitement de la notification de fermeture et le chemin d’enregistrement. En revanche, ne concluez pas à une coupure d’alimentation d’après 41 ou 6008 seuls. Ils indiquent une terminaison inattendue, et un écran bleu ou une réinitialisation forcée sont aussi des candidats.15

Choisir quoi examiner depuis le journal des événements d'arrêtConfirmer le processus initiateur et le motif d'après 1074, et traiter 41 et 6008 comme indices d'une terminaison inattendue à recouper avec des informations environnantes telles que le code de bug check et les dumpsJournal des événements Système1074 : processus initiateur et motifExaminer le traitement de notification et le chemin d'enregistrement41 et 6008 : terminaison inattendueVérifier BugcheckCode et les dumpsIsoler plantage, coupure d'alimentation, etc.

Figure 19 : 41 et 6008 ne sont pas la preuve d’une coupure d’alimentation elle-même ; ils sont le point d’entrée d’une enquête plus poussée.

Un BugcheckCode non nul dans l’événement 41 est un indice de plantage. S’il vaut 0 et qu’il n’y a pas de vidage mémoire, une coupure d’alimentation est suspectée, mais décidez en recoupant avec les informations environnantes. S’il s’agit d’une coupure d’alimentation, concentrez-vous sur la conception d’enregistrement et l’onduleur du chapitre 8 ; s’il s’agissait d’une fermeture planifiée, sur les notifications et le nettoyage des chapitres 3 à 6.

10. Synthèse — concevoir jusqu’au démarrage suivant, pas seulement le traitement de fermeture

Le traitement de l’arrêt n’est pas l’affaire d’un gestionnaire d’événement qui s’exécute une fois à la fermeture. Ce qui compte est de faire de l’enregistrement courant → un nettoyage court → la validation et la récupération au démarrage suivant un tout continu.

Où revoir Point de conception
Traitement courant Enregistrer souvent et réduire le delta restant à la fermeture
Notifications de fermeture graphiques Répondre à la requête par TRUE immédiatement, en principe. Séparer l’enregistrement idempotent du nettoyage post-engagement
Console et services Utiliser la notification qui correspond au modèle d’application. Ne pas se fier seulement à ProcessExit de .NET ou à la prolongation du délai de grâce
Travail ininterruptible Combiner l’enregistrement du motif et le refus seulement aussi longtemps que nécessaire. Se préparer aussi à un arrêt forcé
Enregistrement et chargement En plus du fichier temporaire, du vidage et de l’échange, fournir une sauvegarde et une validation au démarrage
Après un redémarrage Vérifier l’enregistrement au redémarrage, les données de restauration, et le chemin d’ouverture de session ou de démarrage du service

Dans un arrêt avec démarrage rapide activé, le noyau et les pilotes peuvent revenir de la mise en veille prolongée. En isolant un ennui, indiquez explicitement « redémarrer », et faites fonctionner l’application sous un arrêt complet comme sous un hybride.5

Vérification finale du traitement de fermeture à la récupérationGarder un état enregistré pendant le traitement normal, faire un petit nettoyage à une notification de fermeture, et valider les données qui restent même sans notification pour récupérer au démarrage suivantOuiNonGarder un état enregistré courammentY a-t-il une notification de fermeture ?Fermer avec un nettoyage courtLe dernier état enregistré est tout ce qu'il y aValider et récupérer au démarrage suivantRevenir au travail sous les conditions requises

Figure 20 : ne séparez pas l’implémentation de l’événement de fermeture de l’enregistrement courant et de la récupération au démarrage suivant.

Enfin, essayez les chemins de notification et le temps requis sur une machine de test, et confirmez aussi la récupération après un arrêt soudain. Dans l’enquête a posteriori, utilisez 1074 / 41 / 6008 comme indices pour isoler une fermeture planifiée d’une terminaison inattendue.

La prochaine fois que vous ajoutez une fonction, demandez « si une notification de fermeture arrive pendant ce traitement, ou si l’alimentation est arrachée, que restera-t-il au démarrage suivant ? » Inclure cette réponse dans la conception, c’est ce qui vous protège de ne découvrir une corruption de données que le lendemain matin.

Articles connexes

Domaines de conseil associés

KomuraSoft LLC prend en charge la conception et l’implémentation de contre-mesures à l’arrêt et aux coupures d’alimentation pour les PC d’équipement et les applications de longue durée, l’investigation des causes de corruption de données et des pannes « c’était arrêté le matin » qui partent d’un redémarrage de Windows Update ou d’une déconnexion, ainsi que les revues de conception du traitement d’arrêt des services Windows et de la reprise automatique. Vous pouvez commencer dès le stade « quelque chose semble casser à chaque arrêt, mais je ne sais pas par où commencer ».

Références

  1. Microsoft Learn, WM_QUERYENDSESSION message. Sur l’envoi de WM_QUERYENDSESSION à la fin d’une session et le fait que les applications devraient renvoyer TRUE pour respecter l’intention de l’utilisateur (DefWindowProc renvoie aussi TRUE par défaut) ; sur le report du nettoyage jusqu’à WM_ENDSESSION ; sur l’affichage par le système, après 5 secondes, de l’interface listant les applications qui empêchent l’arrêt afin que l’utilisateur puisse forcer la terminaison ; sur la signification des bits ENDSESSION_LOGOFF/CLOSEAPP/CRITICAL de lParam ; sur l’indistinguabilité de l’arrêt et du redémarrage ; et sur l’enregistrement fréquent des données pour réduire la quantité enregistrée à la fermeture. ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  2. Microsoft Learn, HandlerRoutine callback function. Sur les événements CTRL_C/BREAK/CLOSE/LOGOFF/SHUTDOWN reçus par un gestionnaire enregistré avec SetConsoleCtrlHandler ; sur le délai d’expiration par défaut de CTRL_CLOSE_EVENT d’environ 5000 millisecondes et celui de CTRL_SHUTDOWN_EVENT pour les processus de service d’environ 20000 millisecondes ; sur le fait que CTRL_LOGOFF/SHUTDOWN_EVENT n’est reçu en pratique que par les services parce que les applications interactives sont terminées à la déconnexion ; et sur le fait que le gestionnaire s’exécute sur un thread distinct. ↩ ↩2 ↩3 ↩4

  3. Microsoft Learn, Service Control Handler Function. Sur le fait que les services qui déclarent SERVICE_ACCEPT_PRESHUTDOWN reçoivent SERVICE_CONTROL_PRESHUTDOWN en premier, suivis des services SERVICE_ACCEPT_SHUTDOWN qui reçoivent SERVICE_CONTROL_SHUTDOWN ; sur le délai de grâce par défaut à l’arrêt d’environ 20 secondes avec WaitToKillServiceTimeout comme plafond à un redémarrage de l’OS ; sur le fait de ne pas prolonger cette valeur ; sur le fait que le gestionnaire de contrôle revient en 30 secondes, signale STOP_PENDING avec une indication d’attente, et confie le long travail à un autre thread ; sur le fait de terminer le nettoyage aussi vite que possible en tenant compte du fonctionnement sur onduleur ; et sur le fait que le SCM ne considère pas les dépendances à l’arrêt par défaut. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7

  4. Microsoft Learn, .NET runtime no longer provides default termination signal handlers. Sur le fait que le runtime ne fournit plus, à partir de .NET 10, de gestionnaires par défaut pour Windows CTRL_SHUTDOWN_EVENT/CTRL_CLOSE_EVENT (les équivalents de SIGTERM/SIGHUP sous Unix) ; sur le fait que le traitement par défaut de l’OS termine l’application immédiatement de sorte que AppDomain.ProcessExit et AssemblyLoadContext.Unloading ne se déclenchent plus ; et sur le fait que le traitement des signaux adapté au modèle d’application doit être enregistré par des bibliothèques de niveau supérieur ou le code de l’application. ↩ ↩2 ↩3

  5. Microsoft Learn, Fast startup causes hibernation or shutdown to fail in Windows 10 or Windows 8.1. Sur le fait que la session noyau n’est pas fermée mais mise en veille prolongée sous le démarrage rapide, l’état du noyau et des pilotes de périphérique étant enregistré dans hiberfil.sys ; sur le fait que « Redémarrer » effectue toujours un démarrage complet parce qu’un état Windows entièrement nouveau est requis ; sur le fait que le démarrage rapide est activé par défaut et que le désactiver n’est pas recommandé ; et sur le fait que Shutdown.exe a pour défaut un arrêt complet, l’option /hybrid donnant le comportement hybride. ↩ ↩2 ↩3 ↩4 ↩5

  6. Microsoft Learn, Shutdown Changes for Windows Vista. Sur le fait que les réponses à WM_QUERYENDSESSION/WM_ENDSESSION sont retardables de 5 secondes chacune, après quoi l’utilisateur choisit de continuer ou d’annuler ; sur le fait que les applications console et les applications sans fenêtre visible ne peuvent pas annuler l’arrêt et sont terminées automatiquement après 5 secondes sans réponse ou sur une réponse FALSE ; sur l’enregistrement d’un motif avec ShutdownBlockReasonCreate lorsqu’un blocage est nécessaire ; et sur le fait que les applications ne doivent pas dépendre de la capacité à bloquer l’arrêt. ↩ ↩2 ↩3

  7. Microsoft Learn, ShutdownBlockReasonCreate function (winuser.h). Sur l’appel au début d’un travail ininterruptible pour enregistrer une chaîne de motif et l’appel à ShutdownBlockReasonDestroy à l’achèvement ; sur le fait qu’elle n’est appelable que depuis le thread qui a créé la fenêtre ; et sur le fait de garder la chaîne courte et claire parce que l’utilisateur ne lit le motif que quelques secondes. ↩ ↩2 ↩3

  8. Microsoft Learn, SetConsoleCtrlHandler function. Sur le fait qu’un processus qui a chargé gdi32.dll ou user32.dll est traité comme une application Windows dont les gestionnaires CTRL_LOGOFF_EVENT/CTRL_SHUTDOWN_EVENT ne sont pas appelés ; sur le contournement consistant à créer une fenêtre cachée et à traiter WM_QUERYENDSESSION/WM_ENDSESSION ; et sur le fait que les fonctions console peuvent ne pas fonctionner normalement pendant le traitement des signaux. ↩

  9. Microsoft Learn, SERVICE_PRESHUTDOWN_INFO structure (winsvc.h). Sur le fait que le SCM attend après la notification PRESHUTDOWN que le service s’arrête ou expire ; sur le délai par défaut de 10 secondes à partir de Windows 10 Creators Update (build 15063) et de 3 minutes avant ; sur la configuration avec ChangeServiceConfig2 ; et sur le fait que les mises à jour d’état peuvent continuer pendant SERVICE_STOP_PENDING. ↩ ↩2

  10. Microsoft Learn, RegisterApplicationRestart function (winbase.h). Sur l’enregistrement au redémarrage dans les scénarios de plantage, de non-réponse, de mise à jour et de redémarrage d’ordinateur déclenché par une mise à jour ; sur la spécification d’arguments de ligne de commande pour le redémarrage ; sur l’enregistrement avant qu’un problème se produise, le traitement de WM_QUERYENDSESSION étant la dernière occasion dans le scénario de mise à jour ; sur le fait que les processus qui tournent depuis moins de 60 secondes ne sont pas redémarrés ; sur le fait que le redémarrage après un plantage ou un blocage exige le consentement de l’utilisateur ; et sur le fait que traverser un redémarrage de l’OS exige un arrêt avec EWX_RESTARTAPPS/SHUTDOWN_RESTARTAPPS. ↩ ↩2

  11. Microsoft Learn, Winlogon automatic restart sign-on (ARSO). Sur le fait que Windows Update enregistre les informations d’identification du dernier utilisateur interactif et configure Autologon lorsqu’il démarre un redémarrage automatique ; sur le fait que l’utilisateur est ouvert en session automatiquement après le redémarrage et que la session est verrouillée ; sur le fait que les informations d’identification enregistrées sont supprimées après une ouverture de session réussie ; et sur la configuration par stratégie de groupe (DisableAutomaticRestartSignOn et autres). ↩ ↩2

  12. Microsoft Learn, ReplaceFileW function (winbase.h). Sur le fait que ReplaceFile combine en une fonction les plusieurs étapes correspondant à enregistrer dans un nouveau fichier, renommer temporairement l’original, renommer le nouveau fichier et supprimer l’original ; sur le fait qu’il préserve les attributs du fichier original tels que l’heure de création, la DACL, le chiffrement, la compression et les flux nommés ; et sur le fait que la sauvegarde, le fichier remplacé et le fichier de remplacement doivent être sur le même volume. ↩ ↩2

  13. Microsoft Learn, File Caching. Sur le fait que les écritures vont par défaut dans le cache système et sont appliquées au disque par écriture différée ; sur le fait que FILE_FLAG_WRITE_THROUGH écrit les données immédiatement sur le disque ; sur le fait que FlushFileBuffers vide explicitement ; et sur le fait que les métadonnées du système de fichiers sont toujours mises en cache, de sorte que confirmer les métadonnées exige un vidage ou un write-through. ↩ ↩2

  14. Microsoft Learn, PBT_APMPOWERSTATUSCHANGE event. Sur le fait que cet événement est livré par WM_POWERBROADCAST lors d’un basculement entre batterie et alimentation secteur ou lorsque la charge restante baisse ; et sur le fait d’appeler GetSystemPowerStatus à la réception pour vérifier ACLineStatus, BatteryFlag, BatteryLifePercent et d’autres membres de SYSTEM_POWER_STATUS. ↩ ↩2

  15. Microsoft Learn, Troubleshoot unexpected reboots using system event logs. Sur le fait qu’un redémarrage normal enregistre l’ID d’événement 1074 (quel processus a initié l’arrêt, pour qui et pour quel motif) ; sur le fait qu’un redémarrage inattendu enregistre l’ID d’événement 41 (Kernel-Power) et 6008 (l’arrêt précédent était inattendu) ; et sur l’utilisation de ces ID pour isoler le type de redémarrage. ↩ ↩2

Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.

Ces pages replacent le sujet dans un contexte plus large de services et de décisions.

Cet article est directement lié aux services suivants.

Questions fréquentes

Questions souvent posées lors d’une consultation sur le sujet de cet article.

Un problème que « Arrêter » n'a pas corrigé a disparu après « Redémarrer ». Pourquoi ?
Sur les OS clients à partir de Windows 8, lorsque le démarrage rapide est activé (le défaut sur de nombreux PC qui prennent en charge la mise en veille prolongée), « Arrêter » s'appuie sur un mécanisme appelé arrêt hybride. L'utilisateur est déconnecté, mais l'état du noyau et des pilotes est enregistré dans le fichier de mise en veille prolongée et restauré tel quel au démarrage suivant. Autrement dit, le cœur de l'OS n'a pas été réinitialisé. « Redémarrer », en revanche, effectue toujours un démarrage complet, donc les ennuis de pilotes et de services sont réinitialisés. Dans la procédure d'isolement, écrivez explicitement « redémarrer » plutôt que « arrêter et rallumer ». Si vous voulez un arrêt complet depuis la ligne de commande, shutdown /s le fait.
Puis-je retenir l'arrêt jusqu'à ce que l'application finisse d'enregistrer ?
Vous pouvez demander d'attendre temporairement, mais vous ne pouvez pas l'arrêter de façon fiable. Si vous enregistrez une chaîne de motif avec ShutdownBlockReasonCreate uniquement pendant qu'une opération ininterruptible est en cours, ce motif apparaît sur l'écran « Cette application empêche l'arrêt » et l'utilisateur peut décider de continuer ou d'annuler. L'utilisateur peut toutefois encore choisir de forcer la continuation, et un arrêt forcé ou un redémarrage déclenché par une mise à jour peut ne pas attendre du tout. L'approche correcte n'est donc pas de bloquer, mais de réduire les données en risque par une sauvegarde automatique fréquente et de concevoir un nettoyage qui se termine en quelques secondes à partir de la notification de fermeture.
L'arrêt de mon service Windows prend longtemps. Peut-on prolonger le délai de grâce à l'arrêt ?
Dans la configuration par défaut, qui reçoit SERVICE_CONTROL_SHUTDOWN, le délai de grâce est d'environ 20 secondes et dépend de la valeur de Registre WaitToKillServiceTimeout. Réécrire cette valeur depuis l'application pour la prolonger n'est pas recommandé. Si vous avez besoin d'un délai de grâce plus long, vous pouvez déclarer SERVICE_ACCEPT_PRESHUTDOWN et recevoir SERVICE_CONTROL_PRESHUTDOWN ; il est livré avant les autres, et le délai d'expiration se configure avec ChangeServiceConfig2 (le défaut est 10 secondes à partir de Windows 10 Creators Update, et 3 minutes avant). PRESHUTDOWN, toutefois, retient tout l'arrêt pendant cet intervalle, donc limitez-le aux cas dont vous avez vraiment besoin, et concevez fondamentalement le traitement d'arrêt lui-même pour qu'il se termine en quelques secondes.
Est-il prudent de faire le nettoyage d'arrêt dans AppDomain.ProcessExit de .NET ?
Je recommande de ne pas s'y fier. Historiquement le runtime enregistrait des gestionnaires de signal par défaut, et l'événement ProcessExit se déclenchait sur CTRL_CLOSE_EVENT et CTRL_SHUTDOWN_EVENT, mais à partir de .NET 10 le runtime ne fournit plus de gestionnaires de signal de terminaison par défaut, et ProcessExit ne se déclenche plus dans ces situations. Implémentez le nettoyage sur le chemin de notification qui correspond au modèle d'application : pour les applications graphiques, FormClosing ou SessionEnding (ce sont des notifications de phase de requête, donc limitez-les à des enregistrements idempotents et faites le nettoyage qui n'est possible qu'après l'engagement de la fermeture dans un hook WM_ENDSESSION) ; pour Generic Host / Worker Service, IHostApplicationLifetime et StopAsync ; pour les applications console, SetConsoleCtrlHandler ou PosixSignalRegistration.
Comment empêcher que les fichiers soient corrompus par une coupure d'alimentation soudaine ?
Une coupure d'alimentation n'apporte aucune notification du tout, donc la seule option est d'écrire d'une façon qui ne casse pas, peu importe quand l'alimentation est coupée. La base est de ne pas écraser le fichier original sur place : tout écrire dans un fichier temporaire sur le même volume, vider, et échanger avec ReplaceFile (File.Replace en .NET). En fonctionnement normal, cela vous laisse capable de lire celui des anciens ou nouveaux fichiers qui est complet, mais l'atomicité de ReplaceFile à travers une coupure d'alimentation n'est pas garantie par la spécification, donc conservez une sauvegarde (le troisième argument) et implémentez, comme un ensemble, une routine de chargement qui valide le fichier principal au démarrage et retombe sur la sauvegarde s'il est cassé. De plus, le succès de WriteFile ne signifie pas que les données ont atteint le disque, donc aux points de contrôle importants confirmez l'écriture avec FlushFileBuffers ou FILE_FLAG_WRITE_THROUGH. Sur les PC d'équipement, le montage standard ajoute un onduleur, détecte le basculement sur batterie et conduit vers un arrêt sûr.

Profil de l’auteur

Page de présentation de l’auteur de l’article.

Go Komura

Représentant de KomuraSoft LLC

Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.

Retour au blog