Ce qu'est vraiment « Ne répond pas » — comment Windows décide qu'une application est bloquée, et comment concevoir des applications qui ne le sont pas

· · Windows, Développement Windows, Investigation de bugs, Multithreading, WinForms, WPF, Win32 API, Conception d'interface

« L’application devient blanche en cours d’opération et affiche (Ne répond pas). » « Nous recevons des tickets qu’elle se bloque occasionnellement, mais cela ne se reproduit jamais sur une machine de développement. » — Pour les applications métier Windows, ce « Ne répond pas » est l’une des plaintes les plus courantes. Et ce qui est étonnamment peu connu, c’est que la chose qui met l’affichage « Ne répond pas » n’est pas l’application bloquée elle-même — c’est l’OS.

Comment Windows sait-il qu’une application s’est « bloquée » ? Qu’est-ce que cette fenêtre blanc givré ? Destiné aux développeurs qui écrivent des applications métier sous Windows et au personnel informatique qui reçoit des tickets sur des applications qui se bloquent, cet article parcourt le jugement « Ne répond pas » depuis les bases de la boucle de messages, et organise les causes classiques des blocages, les conceptions qui ne se bloquent pas, et une procédure pour investiguer le moment du blocage — le tout ancré dans les sources primaires.

1. La conclusion, d’abord

  • « Ne répond pas » est le jugement de l’OS. Lorsqu’une fenêtre (et le thread GUI qui la possède) n’attend pas d’entrée, n’est pas dans sa séquence de démarrage, et n’a pas récupéré de message (PeekMessage) pendant 5 secondes, l’OS la traite comme ne répondant pas. Le jugement n’est pas par processus.1
  • La fenêtre blanchie est une « fenêtre fantôme ». L’OS a masqué la fenêtre d’origine et a substitué un faux de la même position, taille et apparence. Tout ce que vous pouvez faire est de la déplacer, la réduire ou la fermer ; le contenu ne tourne pas. Une fenêtre fantôme n’est pas créée lorsqu’un débogueur est attaché.2
  • La cause d’un blocage se ramène presque toujours à une seule chose. Le thread d’interface qui devrait pomper la boucle de messages est bloqué sur un travail lourd ou une attente. Les E/S synchrones, les appels réseau, les attentes de verrou et SendMessage entre threads sont les classiques.3
  • Le principe de conception est « n’attendez pas et ne calculez pas sur le thread d’interface ». Déplacez le travail lourd vers un thread worker (async/await + Task.Run en C#) et laissez le thread d’interface consacré à la peinture, à la progression et à l’acceptation de l’annulation.4
  • DoEvents et pomper manuellement la boucle de messages sont un terreau pour les bugs de réentrance. L’affichage « Ne répond pas » disparaît, mais la structure laisse désormais des événements arbitraires interrompre en milieu de travail. La séparation, pas l’esquive, est l’approche correcte.
  • L’investigation commence par capturer l’état au moment du blocage. Prenez un dump et regardez la pile du thread d’interface, et vous pouvez presque toujours identifier ce qu’il attend.

2. Prérequis : les applications Windows sont pilotées par des messages

Pour comprendre « Ne répond pas », il faut d’abord intégrer qu’une application GUI Windows est pilotée par événements. Une application GUI ne va pas chercher l’entrée d’elle-même ; elle reçoit des messages que l’OS délivre (souris, clavier, demandes de repeindre, minuteurs, etc.) et agit dessus.3

Chaque thread qui crée une fenêtre a une file de messages et exécute une boucle de messages comme celle-ci.

MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);   // the window procedure is called
}

GetMessage récupère un message depuis la file, et DispatchMessage appelle la procédure de fenêtre de cette fenêtre (la fonction de traitement des messages). Le traitement d’un clic de bouton, le repeindre, et les gestionnaires d’événements WinForms ou WPF, tous, une fois réduits, s’exécutent à l’intérieur d’une itération de cette boucle.5

Structure de base de la boucle de messagesL'OS place la souris, le clavier et les autres entrées dans la file de messages du thread ; la boucle du thread d'interface la récupère avec GetMessage, appelle la procédure de fenêtre avec DispatchMessage, et revient en haut de la boucle lorsque le traitement se termineOS (entrée, demandes de repeindre, minuteurs)File de messages du threadRécupérer avec GetMessageDispatchMessageTraiter dans la procédure de fenêtre

Figure 1 : le cœur d’une application GUI est la boucle de messages ; chaque gestionnaire d’événement s’exécute comme une itération de cette boucle.

Cette structure a une conséquence importante. Si vous faites un travail long à l’intérieur de la procédure de fenêtre (un gestionnaire d’événement), la boucle ne peut pas récupérer le message suivant entre-temps. Elle ne peut réagir ni aux clics ni aux demandes de repeindre — voilà ce qu’est vraiment un « blocage ».

Il vaut aussi d’intégrer que les messages sont délivrés par deux chemins. PostMessage place le message sur la file et revient immédiatement, et la boucle récupère et traite les messages dans l’ordre. SendMessage, d’un autre côté, appelle la procédure de fenêtre directement et ne revient pas à l’appelant jusqu’à ce que le traitement se termine.36 Cette différence se reporte directement dans la discussion d’interblocage du chapitre 4.

Deux chemins de livraison de messagesPostMessage place le message sur la file et revient immédiatement ; la boucle de messages récupère et traite les messages dans l'ordre. SendMessage appelle la procédure de fenêtre directement et ne revient pas à l'appelant jusqu'à ce que le traitement se terminePostMessagePlacer sur la file (revient immédiatement)La boucle récupère et traite dans l'ordreSendMessageAppeler la procédure directementNe revient pas jusqu'à la fin du traitement

Figure 2 : même si les deux « envoient un message », un Post mis en file et un Send qui attend la fin ont des natures complètement différentes.

3. Comment « Ne répond pas » est jugé — la règle des 5 secondes et la fenêtre fantôme

Alors comment l’OS sait-il que « cette application s’est bloquée » ? Le critère est documenté officiellement. L’OS traite une fenêtre comme ne répondant pas lorsqu’elle n’attend pas d’entrée, n’est pas dans sa séquence de démarrage, et n’a pas appelé PeekMessage (récupération de message) pendant 5 secondes.1 Autrement dit, l’OS surveille si « la boucle de messages tourne réellement » comme on prend un pouls, et s’il n’y a pas de pouls pendant 5 secondes il juge la fenêtre comme ne répondant pas (la documentation indique que cette valeur de 5 secondes peut changer à l’avenir). L’unité de jugement est la fenêtre et le thread GUI qui la possède ; dans une application avec plusieurs threads d’interface, qu’un thread se bloque ne signifie pas que les fenêtres d’un autre thread sont mortes. Le thread à regarder dans un dump est le propriétaire de la fenêtre bloquée.

Ce qui arrive à une fenêtre de premier niveau qui a été jugée est documenté aussi. L’OS masque la fenêtre d’origine et la remplace par une « fenêtre fantôme » qui a le même ordre Z, la même position, taille et apparence. Tout ce que l’utilisateur peut en faire est de la déplacer, la redimensionner ou la fermer (de force). L’application à l’intérieur ne répond pas réellement, donc aucune autre opération ne fonctionne.2

Jugement de fenêtre bloquée et remplacement par une fenêtre fantômeLorsque le thread d'interface est bloqué sur un travail lourd et que la récupération de messages s'arrête pendant 5 secondes, l'OS juge la fenêtre comme ne répondant pas, masque l'originale, substitue une fenêtre fantôme de la même apparence, et n'offre à l'utilisateur que déplacer, réduire et fermernonouiThread d'interface bloqué sur un travail lourdLa récupération de messages s'arrête5 secondes écoulées ?Substituer une fenêtre fantômeLe titre affiche (Ne répond pas)Blanc givré ; seulement déplacer et fermer

Figure 3 : le texte « Ne répond pas » et l’écran blanc appartiennent tous deux à la fenêtre fantôme que l’OS a substituée, pas à l’application bloquée.

La chaîne « (Ne répond pas) » qui apparaît sur la barre de titre, et l’aspect blanc givré sous le thème Aero, appartiennent tous deux à cette fenêtre fantôme. Deux conséquences pratiques suivent.

  • Au moment où « Ne répond pas » est affiché, le thread qui possède cette fenêtre n’a pas traité de messages depuis au moins 5 secondes. Ce n’est pas que « l’affichage est venu trop tôt » — le thread d’interface est définitivement bloqué.
  • Une fenêtre fantôme n’est pas créée lorsqu’un débogueur est attaché.2 Lorsqu’il semble que « cela ne passe jamais en Ne répond pas sous le débogueur, mais oui en version release », le blocage lui-même peut être le même et seul l’affichage différent.

Il y a aussi une API, DisableProcessWindowsGhosting, qui désactive ce remplacement pour tout le processus.7 Elle est destinée à des cas spéciaux tels que des terminaux kiosque où vous ne voulez pas que l’OS fasse sembler une fenêtre opérable d’elle-même. L’appeler arrête l’affichage « Ne répond pas » d’apparaître, mais le fait que l’application soit bloquée ne change pas. Comprenez que ce n’est pas quelque chose qu’une application générale utilise comme contre-mesure à « Ne répond pas ».

4. Pourquoi les applications se bloquent — schémas classiques qui bloquent le thread d’interface

La cause, réduite, est un seul point — « le thread d’interface ne revient pas à la boucle de messages » — mais les formes que vous rencontrez en pratique tombent en quelques classiques.

Classification des causes classiques qui bloquent le thread d'interfaceLes quatre familles classiques — E/S synchrones et appels réseau, attentes de verrou, SendMessage entre threads, et implication COM STA — se ramènent toutes au même point unique que le thread d'interface ne peut pas revenir à la boucle de messagesQuelle cause classique ?E/S ou un verrou ?SendMessage ou COM ?E/S synchrones et réseauAttentes de verrouSendMessageentre threadsImplication COM STAL'interface ne peut pas revenirVérification Ne répond pas

Figure 4 : le symptôme visible est le même, mais le coupable qui bloque le thread tombe en quatre familles, et la contre-mesure diffère pour chacune.

E/S synchrones et appels réseau. C’est le plus courant. Le schéma consistant à faire de façon synchrone, à l’intérieur d’un gestionnaire de clic de bouton, une lecture ou écriture de gros fichier, une requête de base de données, un appel d’API Web, ou l’accès à un fichier sur un lecteur réseau. Sur une machine de développement cela se termine en une fraction de seconde, donc vous ne le remarquez jamais ; la latence réseau de production ou un hoquet de serveur de fichiers en fait une attente de dizaines de secondes, et vous obtenez des tickets que « cela se bloque occasionnellement ». Les lecteurs réseau ont de longs délais d’expiration lorsque la connexion est coupée, et ils aggravent dramatiquement le symptôme.

Attentes de verrou. Le schéma dans lequel le thread d’interface essaie de prendre un verrou sur des données partagées avec un thread worker, et finit par attendre un worker qui détient ce verrou longtemps. La discipline des verrous est couverte en détail dans la série de multithreading en pratique.

SendMessage entre threads. SendMessage ne revient pas jusqu’à ce que la procédure de la fenêtre de destination ait fini de traiter.6 Lorsque vous l’envoyez à une fenêtre sur un autre thread, l’émetteur est mis à attendre jusqu’à ce que ce thread soit dans un état où il peut traiter des messages. Si le thread de destination attend lui-même quelque chose, vous avez un interblocage de messages dans lequel chaque côté attend l’autre.3 Envoyer à HWND_BROADCAST en particulier vous entraînera dès qu’une seule fenêtre ne répond pas. Lorsque vous ne pouvez pas vous permettre d’attendre, envisagez SendMessageTimeout ou PostMessage, qui n’attend pas de réponse.8

Interblocage causé par SendMessage entre threadsSi un thread worker envoie SendMessage à une fenêtre du thread d'interface alors que le thread d'interface est bloqué en attendant le résultat du worker, chaque côté attend que l'autre se termine et vous avez un interblocageThread workerThread d'interfaceThread workerThread d'interfaceNe peut pas traiter les messages (bloqué)Ne peut pas revenir de SendMessageAttente mutuelle — interblocageEn attente que le worker se termine (bloqué)SendMessage (ne revient pas jusqu'au traitement)

Figure 5 : « le thread d’interface attend le worker, et le worker attend le thread d’interface via SendMessage » est un interblocage classique.

Implication d’appartement COM. Les appels à un objet STA sont délivrés comme des messages de fenêtre, donc lorsque le thread d’interface (STA) est bloqué, les appels COM depuis d’autres threads se bloquent aussi en collatéral. Cette structure est expliquée dans l’article COM STA/MTA.

L’accumulation de « ce n’est qu’un instant ». Même un appel synchrone de 50 ms, invoqué 100 fois dans une boucle, fait 5 secondes. Le seuil de Ne répond pas est de 5 secondes, mais la « lenteur » perçue commence autour de 100 ms. Une règle de pouce de conception est « le thread d’interface ne peut être bloqué que pendant des millisecondes ».

5. Des conceptions qui ne se bloquent pas — déplacer le travail lourd hors du thread d’interface

Le principe de conception tient en une chose : déplacez le travail long hors du thread d’interface. En C# (WinForms/WPF), async/await est l’approche correcte la plus courte.

private async void RunButton_Click(object sender, EventArgs e)
{
    runButton.Enabled = false;
    try
    {
        // CPU-heavy work, or APIs that are only synchronous, go to a worker via Task.Run
        var result = await Task.Run(() => HeavyCalculation(input));

        // For I/O, use APIs that are natively async (they do not consume a thread either)
        var data = await httpClient.GetStringAsync(url);

        // After await you are back on the UI thread, so you can touch controls directly
        resultLabel.Text = result + data.Length.ToString();
    }
    catch (Exception ex)
    {
        // An exception leaking from an async void handler will take the app down. Catch it here
        MessageBox.Show($"The operation failed: {ex.Message}");
    }
    finally
    {
        runButton.Enabled = true;
    }
}

Il y a trois points. Premier, pendant que await attend, le thread d’interface est de retour dans la boucle de messages, donc vous ne passez pas en Ne répond pas. Deuxième, la continuation après await revient au thread d’interface, donc vous pouvez toucher les contrôles normalement ensuite (toucher un contrôle directement depuis un thread worker est interdit ; si vous en avez besoin, utilisez Control.Invoke / Dispatcher.InvokeAsync).4 Troisième, désactivez le bouton pendant que le travail tourne, et autrement tuez la réentrance par conception.

Le tableau est le même en Win32 natif : donnez le travail à un thread worker, notifiez le thread d’interface de la fin comme un message personnalisé via PostMessage, et mettez à jour l’interface dans la procédure de fenêtre. PostMessage ne fait que placer le message sur la file et revient immédiatement, donc le côté worker n’est pas bloqué non plus.6 Écrivez l’attente du thread worker elle-même avec la discipline couverte dans l’article sur les variables de condition.

Division des rôles dans une application qui ne se bloque pasLe thread d'interface n'est responsable que d'accepter l'entrée, d'afficher la progression et d'accepter l'annulation ; un thread worker exécute le travail lourd et renvoie la fin au thread d'interface via PostMessage ou une continuation awaitRemettre le travailPostMessage / continuation awaitThread d'interface : entrée, progression, annulerThread worker : travail lourdPas d'E/S synchrones ni de long calcul sur le thread d'interface

Figure 6 : gardez le thread d’interface comme le « bureau d’accueil », donnez toujours le travail lourd à un worker, et ne prenez que la notification de fin.

Ce que vous voulez éviter, c’est la technique d’insérer Application.DoEvents() ou une boucle PeekMessage entre des morceaux de travail lourd juste pour garder l’affichage vivant. Vous esquivez Ne répond pas, mais des gestionnaires d’événements arbitraires réentrent au milieu du travail. Un second clic du bouton, fermer le formulaire pendant le traitement, un minuteur qui se déclenche — n’importe lequel peut corrompre des données encore en cours de traitement, et les bugs sont dépendants du moment et difficiles à reproduire. Gardez le pompage manuel de la boucle de messages à l’intérieur d’une structure limitée telle qu’une boîte de dialogue de progression modale, et en règle générale résolvez-le par la séparation.

Chronologie d'un bug de réentrance causé par DoEventsAppeler DoEvents au milieu d'un travail lourd laisse le gestionnaire d'événement d'un clic en file interrompre et s'exécuter, réécrire des données encore en cours de traitement, puis reprendre le travail d'origine, produisant une corruption de données dépendante du momentThread d'interfaceThread d'interfaceLe gestionnaire de reclic du bouton interromptLe travail lourd commence (données en cours de traitement)DoEvents (traiter les messages en file)Le travail interruptif réécrit les donnéesLe travail d'origine reprend (données déjà incohérentes)

Figure 7 : DoEvents efface « Ne répond pas » en échange d’inviter des événements arbitraires au milieu du travail.

Pour un travail de longue durée, incluez aussi l’affichage de progression et l’annulation dans la conception. Envoyez la progression à l’interface avec IProgress<T> et communiquez l’interruption avec un CancellationToken, et l’utilisateur peut voir que « ça travaille » et n’ira pas chercher une terminaison forcée (qui est souvent une cause de corruption de données).

Flux de progression et d'annulation pour un travail de longue duréeLe thread worker envoie la progression au thread d'interface via IProgress ; une action d'annulation sur l'interface atteint le worker à travers un CancellationToken ; le worker s'arrête à une frontière commode et nettoieProgression via IProgressCancellationTokenWorker : travail de longue duréeInterface : progression et un bouton ArrêterS'arrêter à une frontière et nettoyer

Figure 8 : la progression est « worker → interface » ; l’annulation est « interface → worker ». Incluez ce mince canal bidirectionnel dans la conception dès le départ.

6. Investiguer le moment du blocage

Dans une investigation de « cela se bloque occasionnellement », la chose la plus précieuse est l’état des threads au moment exact où c’est bloqué. Redémarrez, et la preuve est partie.

Prenez un dump. Sur l’onglet Détails du Gestionnaire des tâches, clic droit sur le processus cible → « Créer un fichier de vidage ». Cela seul vous donne un dump complet avec la pile de chaque thread. Dire simplement au personnel informatique qui reçoit les tickets « lorsqu’elle se bloque, prenez ceci avant de la fermer » change beaucoup le taux de succès de l’investigation. Pour construire un mécanisme de collecte, voir l’article sur la collecte de dumps de plantage.

Regardez la pile du thread d’interface. Ouvrez le dump dans WinDbg et regardez la pile du thread qui pompe la boucle de messages (habituellement le thread 0). Les E/S synchrones apparaissent comme ReadFile ou une API réseau, une attente de verrou comme un appel de la famille WaitFor…, et SendMessage entre threads comme une attente à l’intérieur de SendMessage — tel quel. Comment le lire est expliqué dans l’article d’introduction à WinDbg.

Regardez-le en direct. Avec Process Explorer vous pouvez inspecter la liste de threads et les piles sur place. Lorsqu’il est régulièrement lent, prenez une trace WPR et analysez les attentes du thread d’interface dans le temps (WPR/WPA en pratique).

Procédure de base pour investiguer Ne répond pasPrenez un dump au moment du blocage, regardez la pile du thread d'interface, identifiez s'il est arrêté sur des E/S synchrones, une attente de verrou, ou SendMessage entre threads, et reliez cela au correctif de conception correspondantLe moment du blocagePrendre un dump (avant de fermer)Regarder la pile du thread d'interfaceE/S synchrones ou attente réseauAttente de verrouSendMessage entre threadsSéparer le site vers un worker

Figure 9 : la vedette de l’investigation est « un dump du moment du blocage » ; la pile du thread d’interface est elle-même la classification de la cause.

Vous pouvez aussi standardiser la façon dont vous prenez une première coupe depuis le symptôme. Si elle se bloque toujours sur une opération particulière, suspectez d’abord des E/S synchrones à l’intérieur de ce gestionnaire. Si elle se bloque rarement et sans corrélation avec une opération, suspectez un ordre de verrous ou un interblocage SendMessage entre threads, et appariez les cibles d’attente des deux threads dans le dump. Si elle ne se bloque que dans un environnement particulier, suspectez des délais d’expiration provenant de facteurs environnementaux tels qu’un lecteur réseau, un proxy ou un logiciel antivirus.

7. Résumé

  • « Ne répond pas » est un mécanisme dans lequel l’OS juge qu’une application n’a pas récupéré de message pendant 5 secondes et substitue une fenêtre fantôme. La chose qui met l’affichage est l’OS, pas l’application.
  • La cause d’un blocage est un seul point : « le thread d’interface ne peut pas revenir à la boucle de messages ». Les E/S synchrones, le réseau, les attentes de verrou et SendMessage entre threads sont les classiques.
  • La contre-mesure est de déplacer le travail lourd hors du thread d’interface. En C#, async/await + Task.Run ; en Win32, un thread worker + PostMessage. Empêchez la réentrance pendant l’exécution par conception, tel que désactiver le bouton.
  • L’esquiver avec DoEvents est en échange de bugs de réentrance. DisableProcessWindowsGhosting ne fait que retirer l’affichage. Ni l’un ni l’autre n’est un correctif de cause profonde.
  • Pour l’investigation, un dump du « moment du blocage » est la chose la plus importante. La cause est presque toujours écrite telle quelle sur la pile du thread d’interface.

Du point de vue de l’utilisateur « Ne répond pas » est « c’est cassé », mais une fois que vous connaissez le mécanisme vous pouvez le traduire en la phrase précise « le thread d’interface n’est pas revenu pendant 5 secondes ». En remontant depuis cette seule phrase, les causes candidates, le correctif et la procédure d’investigation tombent tous naturellement.

Articles connexes

Domaines de conseil associés

KomuraSoft LLC prend en charge l’investigation des causes profondes d’applications métier qui « se bloquent occasionnellement » ou passent en « Ne répond pas » (analyse de dump et analyse de traces), le refactoring de code d’interface héritée plein de travail synchrone vers async/await et la séparation en threads worker, et les revues de conceptions d’interface qui ne gèlent pas. Même lorsque vous n’avez pas encore de procédure de reproduction, nous pouvons aider à partir de la conception de la façon de collecter les preuves.

Références

  1. Microsoft Learn, IsHungAppWindow function (winuser.h). Sur le critère de jugement qu’une application est traitée comme ne répondant pas lorsqu’elle « n’attend pas d’entrée, n’est pas dans sa séquence de démarrage, et n’a pas appelé PeekMessage pendant le délai d’expiration interne de 5 secondes » ; sur le fait que ce critère de 5 secondes est sujet à changement ; et sur le fait que la fonction renvoie toujours TRUE pour une fenêtre fantôme.  2

  2. Microsoft Learn, GetMessage function (winuser.h). Sur le système qui traite une fenêtre de premier niveau comme ne répondant pas lorsqu’elle cesse de répondre aux messages pendant plusieurs secondes et la remplace par une fenêtre fantôme du même ordre Z, position, taille et apparence ; sur le fait que l’utilisateur ne peut que la déplacer, la redimensionner ou la fermer ; et sur le fait qu’une fenêtre fantôme n’est pas créée lorsqu’un débogueur est attaché.  2 3

  3. Microsoft Learn, About Messages and Message Queues. Sur le fait que les applications Windows sont pilotées par événements et que la procédure de fenêtre traite les messages ; sur la distinction entre messages mis en file et messages envoyés directement ; sur le remplacement d’une fenêtre ne répondant pas par une fenêtre fantôme ; et sur la section couvrant l’interblocage de threads qui s’envoient des messages les uns aux autres.  2 3 4

  4. Microsoft Learn, How to make thread-safe calls to controls (Windows Forms). Sur le fait que les contrôles WinForms ne sont pas sûrs à toucher depuis un thread autre que celui qui les a créés ; sur l’utilisation de Invoke/BeginInvoke pour les mises à jour depuis un autre thread ; et sur les schémas asynchrones sûrs utilisant async/await ou BackgroundWorker.  2

  5. Microsoft Learn, Using Messages and Message Queues. Sur une implémentation typique de boucle de messages avec GetMessage, TranslateMessage et DispatchMessage, et sur la façon d’inspecter une file de messages. 

  6. Microsoft Learn, SendMessage function (winuser.h). Sur le fait que SendMessage appelle la procédure de fenêtre de la fenêtre spécifiée et ne revient pas jusqu’à ce que le traitement se termine ; sur le fait qu’un envoi à une fenêtre sur un autre thread met l’émetteur à attendre jusqu’à ce que ce thread traite le message ; et sur la différence avec PostMessage, qui place le message sur la file sans attendre de réponse.  2 3

  7. Microsoft Learn, DisableProcessWindowsGhosting function (winuser.h). Sur le fait de pouvoir désactiver, pour le processus GUI appelant, la fonctionnalité de fenêtre fantôme qui rend une fenêtre ne répondant pas réductible, déplaçable et fermable ; et sur le fait que la désactivation dure pour la durée de vie du processus. 

  8. Microsoft Learn, SendMessageTimeout function (winuser.h). Sur le fait de pouvoir envoyer un message avec un délai d’expiration ; et sur un drapeau (SMTO_ABORTIFHUNG) qui revient sans attendre lorsque la fenêtre ne répond pas (a été jugée bloquée). 

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.

Dans quelles conditions « Ne répond pas » apparaît-il ?
L'OS juge une fenêtre bloquée lorsqu'une application fenêtrée n'attend pas d'entrée, n'est pas dans sa séquence de démarrage, et n'a pas récupéré de message (PeekMessage) pendant 5 secondes. La fenêtre de premier niveau bloquée est masquée et remplacée par une « fenêtre fantôme » de la même position, taille et apparence. Le texte de barre de titre « (Ne répond pas) » et l'aspect blanc givré appartiennent à cette fenêtre fantôme, qui ne vous laisse que déplacer, réduire ou fermer. Autrement dit, « Ne répond pas » n'est pas quelque chose que l'application elle-même affiche — c'est un écran que l'OS met à la place de l'application.
Y a-t-il un réglage pour empêcher « Ne répond pas » d'apparaître pendant qu'un travail est en cours ?
Appeler DisableProcessWindowsGhosting désactive le remplacement par une fenêtre fantôme pour ce processus. Cela ne fait toutefois que rendre le blocage moins visible pour l'utilisateur — la fenêtre ne réagit toujours pas à l'entrée, et du point de vue de l'utilisateur c'est un gel complet sans moyen de déplacer ou de fermer. Le vrai correctif n'est pas de supprimer l'affichage, mais de déplacer le travail lourd vers un thread worker afin que le thread d'interface ne soit jamais bloqué même un dixième de seconde, encore moins cinq. Notez aussi que l'OS ne crée pas de fenêtre fantôme lorsqu'un débogueur est attaché, donc il peut sembler que « Ne répond pas » n'arrive jamais pendant le débogage.
Est-il acceptable d'éviter « Ne répond pas » avec DoEvents (en pompant manuellement la boucle de messages) ?
Ce n'est pas recommandé. Faire tourner DoEvents ou une boucle PeekMessage au milieu d'un travail lourd esquivera le jugement de fenêtre bloquée, mais n'importe quel gestionnaire d'événement peut alors réentrer — un second clic du bouton, fermer la fenêtre, un minuteur, etc. Un autre gestionnaire qui réécrit des données encore en cours de traitement, ou qui touche un formulaire censé être fermé et lève une exception, produit des bugs de réentrance dépendants du moment et difficiles à reproduire — pires que « Ne répond pas » lui-même. L'approche correcte est de déplacer le travail lui-même vers un thread worker avec Task.Run ou analogue, et de laisser le thread d'interface responsable seulement de l'affichage de progression et de l'acceptation de l'annulation.
Comment mettre à jour l'interface (les contrôles) depuis un thread worker ?
Les contrôles WinForms et les éléments WPF ne peuvent être touchés que depuis le thread qui les a créés (normalement le thread d'interface). Les toucher directement depuis un thread worker provoque des exceptions ou un comportement indéfini. En C#, async/await est le chemin le plus facile : la continuation après await revient au thread d'interface appelant, donc vous pouvez mettre à jour les contrôles normalement après l'await. Pour basculer explicitement, utilisez Control.Invoke/BeginInvoke en WinForms et Dispatcher.InvokeAsync en WPF. En Win32 natif, le schéma établi est que le thread worker PostMessage un message de fin personnalisé au thread d'interface, et que la procédure de fenêtre mette à jour l'interface.
Comment investiguer pourquoi une application affiche « Ne répond pas » ?
L'important est de capturer l'état au « moment lui-même » du blocage. Prenez d'abord un dump complet depuis l'onglet Détails du Gestionnaire des tâches avec « Créer un fichier de vidage », puis dans WinDbg regardez la pile du thread d'interface (le thread qui exécute la boucle de messages). Qu'il soit coincé dans des E/S synchrones, une attente réseau, une attente de verrou, ou une attente sur un autre thread via SendMessage apparaît tel quel sur la pile. Pour regarder un processus en direct, la liste de threads et la vue de pile de Process Explorer sont utiles ; pour le suivre dans le temps, capturer une trace WPR est efficace. Voir aussi l'article d'introduction à WinDbg de ce site, Process Explorer en pratique, et WPR/WPA en pratique.

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