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

· Mis à jour le: · · Windows, Développement Windows, Investigation de bugs, Multithreading, WinForms, WPF, Win32 API, Conception d'interface

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

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). Ce qu'est vraiment « Ne répond pas » — comment Windows juge qu'une application est bloquée, et comment concevoir des applications qui ne le sont pas. KomuraSoft LLC. https://comcomponent.com/fr/blog/windows-app-not-responding-hang-mechanism/

DOI (archive enregistrée)
10.5281/zenodo.22176665
DOI (dernière version enregistrée)
10.5281/zenodo.22176666

« Pendant le traitement, l’écran devient blanc et la barre de titre affiche “Ne répond pas”. » « Les utilisateurs disent que ça se bloque de temps en temps, mais ça ne se reproduit jamais sur une machine de développement. » Ce sont des plaintes courantes pour les applications métier Windows.

Le premier point à retenir est que ce n’est pas l’application elle-même qui affiche « Ne répond pas », c’est Windows. Windows détecte que le traitement des messages de la fenêtre s’est arrêté et remplace l’écran d’origine par une fenêtre de substitution. La clé pour remonter à la cause est ce que fait le thread d’interface propriétaire de cette fenêtre, qui l’empêche de traiter le message suivant.12

Cet article s’adresse aux développeurs qui créent des applications métier sous Windows et aux responsables informatiques qui reçoivent des tickets sur des applications bloquées. Il procède dans l’ordre jugement de l’OS → boucle de messages → isolement de la cause par catégorie → conceptions qui ne se bloquent pas → procédure d’investigation. Si vous devez investiguer une application actuellement bloquée, lisez d’abord le chapitre 7.

1. La conclusion d’abord : ne pas masquer l’affichage, libérer le thread d’interface

Le mécanisme de « Ne répond pas », la façon de le corriger et la façon de l’investiguer deviennent plus clairs lorsqu’on les sépare ainsi.

Ce que vous voulez savoir ou résoudre Le premier point à retenir Explication détaillée
Que regarde Windows pour juger qu’une application ne répond pas ? Il regarde la fenêtre et le traitement des messages du thread GUI qui la possède. Le jugement n’est pas à l’échelle du processus Chapitre 2
Pourquoi les boutons et le repeindre s’arrêtent-ils ? Le thread d’interface ne revient pas d’un traitement d’événement ou d’une attente, donc il ne peut pas récupérer le message suivant Chapitres 3 et 4
Je ne veux pas geler l’écran pendant un travail long Déplacer le calcul CPU et les API uniquement synchrones vers un worker ; utiliser des API asynchrones pour les E/S Chapitre 5
Peut-on seulement éviter l’affichage avec DoEvents ou un réglage ? Cela invite des bugs de réentrance ou ne fait que masquer l’affichage. Ce n’est pas un correctif de cause Chapitre 6
Trouver la cause d’un blocage occasionnel Prendre un dump au moment du blocage, avant de quitter ou de redémarrer Chapitre 7

Le principe de la contre-mesure est de ne pas attendre longtemps et de ne pas calculer lourdement sur le thread d’interface. Le thread d’interface s’occupe de l’entrée, du dessin, de l’affichage de progression et de l’acceptation de l’annulation, et est séparé du travail qui prend du temps.3

De plus, le délai jusqu’à l’affichage « Ne répond pas » et le temps de réponse confortable à l’usage sont deux choses distinctes. Rester sous 5 secondes ne suffit pas. La section 4.5 explique cette différence.

2. « Ne répond pas » est le jugement de l’OS : la règle des 5 secondes et l’écran de substitution

2.1 L’unité de jugement n’est pas le processus entier, mais la fenêtre et le thread propriétaire

Dans la description Microsoft de IsHungAppWindow, une fenêtre est considérée comme ne répondant pas lorsqu’elle satisfait les conditions suivantes.1

Condition Contenu
N’attend pas d’entrée Elle n’est pas dans un état d’attente d’entrée
Pas dans le traitement de démarrage L’application n’est pas dans son traitement de démarrage
Ne récupère pas de messages Elle n’a pas appelé PeekMessage pendant le délai interne de 5 secondes

L’OS ne regarde pas ce que l’application calcule, mais si la boucle de messages tourne. La boucle de messages est le mécanisme qui récupère dans l’ordre les demandes d’entrée et de repeindre et les traite. Le chapitre 3 explique le fonctionnement concret.

La même documentation indique aussi que la valeur de 5 secondes peut changer à l’avenir. C’est le délai interne du jugement de non-réponse, pas une règle de conception qui dirait « vous pouvez bloquer l’interface jusqu’à 5 secondes ».1

Le jugement n’est pas par processus. Dans une application qui a plusieurs threads d’interface, une fenêtre peut être bloquée tandis que des fenêtres appartenant à d’autres threads continuent de fonctionner. Dans l’investigation aussi, il ne suffit pas de regarder le processus : il faut identifier le thread propriétaire de la fenêtre bloquée.

2.2 L’écran blanc givré est une « fenêtre fantôme »

Lorsqu’une fenêtre de premier niveau est jugée comme ne répondant pas, Windows masque la fenêtre d’origine et la remplace par une fenêtre fantôme de même ordre Z, position, taille et apparence. Le « (Ne répond pas) » du titre et l’aspect blanc givré sous le thème Aero viennent de cet écran de substitution, pas de l’application bloquée.2

État de l’écran Ce que voit l’utilisateur
La fenêtre d’origine de l’application Ne peut pas traiter les messages ; ne réagit ni aux clics ni au repeindre
La fenêtre fantôme fournie par l’OS Opérations limitées : déplacer, redimensionner, réduire, fermer

Pouvoir déplacer la fenêtre de substitution ne signifie pas que le contenu de l’application a recommencé à fonctionner. L’OS ne fait que prendre en charge le minimum d’opérations.24

Une réclamation du type « l’affichage apparaît trop tôt » se traite d’abord comme un problème de thread d’interface qui ne revient pas longtemps au traitement des messages. Investiguer le traitement arrêté passe avant de supprimer l’affichage.

2.3 Ne pas le voir sous le débogueur ne veut pas dire que ce n’est pas bloqué

Tant qu’un débogueur est attaché, l’OS ne crée pas de fenêtre fantôme. Un écart du type « ça n’apparaît pas en débogage, mais ça passe à Ne répond pas en exécution normale » peut donc masquer le même blocage du thread d’interface, seule l’affichage différant.2

Il existe aussi une API qui désactive le remplacement par une fenêtre fantôme à l’échelle du processus, mais ce n’est pas une API qui corrige le blocage lui-même. La section 6.2 sépare l’usage prévu.

3. Pourquoi l’écran s’arrête : le thread d’interface et la boucle de messages

3.1 L’entrée et le repeindre sont traités par le même thread

Les applications GUI Windows sont pilotées par événements. Elles reçoivent des messages pour la souris, le clavier, les demandes de repeindre, les minuteurs, etc., et exécutent le traitement correspondant. Chaque thread qui crée une fenêtre a une file de messages et exécute une boucle comme celle-ci.5

MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);   // la procédure de fenêtre est appelée
}

GetMessage récupère un message depuis la file, et DispatchMessage appelle la procédure de fenêtre de la fenêtre. La procédure de fenêtre est la fonction qui exécute le traitement correspondant à un message. Le traitement d’un clic de bouton, le repeindre, et les gestionnaires d’événements WinForms et WPF, tous se rattachent à ce traitement de messages sur le thread d’interface.6

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 : récupérer un message, le traiter dans la procédure de fenêtre, et revenir à la boucle. Ce cycle porte l’entrée et le dessin.

Que se passe-t-il alors si un traitement long est appelé depuis un gestionnaire de clic ? Tant que ce traitement n’est pas revenu, le thread d’interface ne peut pas récupérer le message suivant. Les clics et les demandes de repeindre qui arrivent ensuite ne peuvent plus être traités. C’est la structure de base d’un « écran gelé ».

3.2 PostMessage et SendMessage ne reviennent pas au même moment

La livraison des messages a deux chemins : l’un qui empile le message dans la file, et l’un qui attend la fin du traitement.57

API Comportement Moment où l’appelant revient
PostMessage Empile le message dans la file ; le destinataire le récupère et le traite Revient une fois le message empilé. N’attend pas la fin du traitement
SendMessage Envoie le message à la procédure de fenêtre et la fait exécuter Ne revient pas tant que la procédure de fenêtre n’a pas fini

En particulier, lorsque SendMessage est envoyé à une fenêtre d’un autre thread, l’émetteur est lui aussi mis en attente jusqu’à ce que le destinataire puisse traiter le message. Cette différence mène directement à l’interblocage de la section 4.3.7

4. Causes par catégorie : cinq schémas qui bouchent le thread d’interface

L’apparence de « Ne répond pas » est la même, mais ce qui bouche diffère. Séparez d’abord les candidats, puis confirmez-les avec les dumps et traces du chapitre 7.

Cause candidate Ce qui se passe sur le thread d’interface Apparition typique
E/S synchrones ou appel réseau Attend la fin d’un fichier, d’une base, d’une API Web, etc. Rapide sur la machine de développement, mais se bloque en production ou dans un environnement particulier
Attente de verrou Ne peut pas acquérir un verrou tenu par un worker et attend Se bloque rarement, selon le chevauchement des traitements
SendMessage entre threads Attend qu’un autre thread traite le message Se retrouve dans une attente mutuelle ou dans un broadcast
Appel vers un STA COM Attend le traitement des messages du STA cible L’arrêt du thread d’interface se propage aux appels COM des autres threads
Enchaînement de traitements courts Chacun est court, mais l’exécution en série ne revient pas à la boucle L’opération ne devient lourde que lorsque le nombre d’éléments augmente

4.1 E/S synchrones et appels réseau

Le schéma le plus fréquent consiste, dans le gestionnaire de clic d’un bouton, à faire de façon synchrone la lecture ou l’écriture d’un gros fichier, une requête de base de données, une API Web, ou l’accès à un lecteur réseau.

Ce qui se termine en une fraction de seconde sur la machine de développement peut devenir une attente de plusieurs dizaines de secondes sous la latence réseau de production ou un problème du serveur de fichiers. Les lecteurs réseau ont des délais d’expiration longs en cas de coupure, ce qui aggrave le symptôme. « C’était rapide sur la machine de développement » n’est pas une raison d’attendre sur le thread d’interface.

4.2 Le thread d’interface attend un verrou tenu par un worker

Les verrous qui protègent des données partagées peuvent aussi arrêter l’interface. Si un worker tient un verrou longtemps et que le thread d’interface essaie de le prendre, le thread d’interface attend jusqu’à pouvoir l’acquérir.

Même après avoir déplacé le travail vers un worker, l’écran ne se libère pas si le thread d’interface attend la fin de ce travail ou la libération du verrou. La discipline des verrous est traitée en détail dans la série pratique sur le multithreading.

4.3 S’attendre mutuellement via SendMessage

L’interblocage classique est la combinaison dans laquelle le thread d’interface attend la fin d’un worker, et ce worker envoie SendMessage à l’interface et attend. L’interface est en attente et ne peut pas traiter les messages, et le worker ne peut pas revenir de SendMessage, donc aucun des deux n’avance.75

Interblocage par SendMessage entre threadsLorsqu'un thread worker envoie SendMessage à la fenêtre du thread d'interface alors que celui-ci est bloqué en attendant le résultat du worker, chacun attend la fin de l'autre et il en résulte un interblocageThread workerThread d'interfaceThread workerThread d'interfaceNe peut pas traiter les messages (en attente)Ne peut pas revenir de SendMessageAttente mutuelle : interblocageAttendre la fin du worker (bloqué)SendMessage (ne revient pas avant la fin du traitement)

Figure 2 : déplacer le travail vers un worker ne suffit pas. Si l’interface et le worker attendent chacun la fin de l’autre, il y a interblocage.

L’envoi vers HWND_BROADCAST demande aussi de la prudence. Une seule fenêtre qui ne répond pas suffit à entraîner l’émetteur. Lorsque vous ne pouvez pas continuer à attendre, envisagez SendMessageTimeout, qui pose un délai, ou PostMessage, qui n’attend pas la fin du traitement.8

4.4 Un STA COM s’arrête et entraîne d’autres threads

Les appels d’autres threads vers un objet STA sont livrés comme messages de fenêtre. Par conséquent, lorsque le thread d’interface (un STA) est bouché, les appels COM vers lui se bloquent aussi. C’est une structure dans laquelle non seulement l’interface, mais aussi les appelants, se retrouvent en attente.

La relation entre appartements et traitement des messages est expliquée dans l’article COM STA/MTA.

4.5 Répéter « une fois, c’est un instant » 100 fois

Même un traitement synchrone de 50 ms fait 5 secondes s’il est répété 100 fois. Ne jugez pas en mesurant chaque fonction isolément et en la trouvant courte ; pensez en temps total jusqu’au retour du thread d’interface à la boucle de messages.

La sensation de lenteur commence vers 100 ms. Ne visez pas le seuil de 5 secondes du jugement de non-réponse ; concevez en considérant que le thread d’interface ne peut être bouché que pendant des millisecondes.

5. Conceptions qui ne se bloquent pas : séparer l’exécution du travail et la mise à jour de l’écran

5.1 Séparer le calcul et les API synchrones des E/S asynchrones

Le principe de la contre-mesure est de sortir le travail qui prend du temps du thread d’interface. Tout n’est toutefois pas déplacé de la même façon.

Type de travail Traitement de base en C# Rôle du thread d’interface
Calcul à forte charge CPU Le déplacer vers un thread worker avec Task.Run Attendre la fin de façon asynchrone et afficher le résultat
Traitement qui n’a qu’une API synchrone Le séparer sur un thread worker Ne pas boucher l’interface en attendant la fin de façon synchrone
E/S avec une API asynchrone await GetStringAsync ou analogue Revenir au traitement des messages pendant l’attente des E/S
Entrée, dessin, progression, annulation Les prendre en charge sur le thread d’interface Ne pas y mélanger un long calcul ni des E/S synchrones

Avec des E/S asynchrones, il n’est pas nécessaire d’occuper un thread uniquement pour attendre. L’exemple de code ci-dessous aussi distingue : Task.Run pour le calcul, API asynchrone native pour la communication HTTP.3

5.2 En C#, revenir à l’interface avec async/await et la mettre à jour

L’exemple suivant sépare, dans un gestionnaire de clic WinForms, le travail lourd et la mise à jour de l’interface. Parce que cet exemple part d’un gestionnaire d’événement d’interface et conserve le contexte d’exécution de l’interface, la continuation après await revient sur le thread d’interface et peut mettre à jour les contrôles.3

private async void RunButton_Click(object sender, EventArgs e)
{
    runButton.Enabled = false;
    try
    {
        // Calcul CPU lourd et API uniquement synchrones : vers un worker via Task.Run
        var result = await Task.Run(() => HeavyCalculation(input));

        // Pour les E/S, utiliser une API nativement asynchrone (elle ne consomme pas de thread non plus)
        var data = await httpClient.GetStringAsync(url);

        // Après await on est de retour sur le thread d'interface, on peut toucher les contrôles directement
        resultLabel.Text = result + data.Length.ToString();
    }
    catch (Exception ex)
    {
        // Une exception qui fuit d'un gestionnaire async void fait planter l'application. L'attraper ici
        MessageBox.Show($"Le traitement a échoué : {ex.Message}");
    }
    finally
    {
        runButton.Enabled = true;
    }
}

Quatre points à retenir.

Endroit Intention
Attendre la fin avec await Rendre le thread d’interface à la boucle de messages dès qu’une attente survient
Mettre à jour l’écran après await Ne pas toucher les contrôles depuis le worker
Désactiver le bouton pendant l’exécution Empêche le même traitement d’être relancé plusieurs fois
catch et finally Ne pas laisser fuir d’exception hors du gestionnaire async void, et rétablir l’état du bouton

C’est un exemple de code qui montre la répartition des rôles ; l’implémentation de HeavyCalculation et analogues, la gestion de la fermeture du formulaire, ainsi que la progression et l’annulation, sont omises. La progression et l’interruption nécessaires à un travail long s’ajoutent à part, comme en section 5.4.

Les contrôles WinForms et les éléments WPF se manipulent depuis le thread qui les a créés. Les toucher directement depuis un worker mène à des exceptions ou à un comportement indéfini. Pour revenir explicitement au thread d’interface, utilisez Control.Invoke / BeginInvoke en WinForms et Dispatcher.InvokeAsync en WPF.3

5.3 En Win32, notifier la fin avec PostMessage

La répartition des rôles est la même en Win32 natif. Passez le travail à un worker, et une fois terminé, envoyez avec PostMessage un message de fin personnalisé et mettez à jour l’écran dans la procédure de fenêtre du thread d’interface. Comme PostMessage revient une fois le message empilé, le worker n’attend pas la fin du traitement de l’interface.7

Répartition des rôles d'une application qui ne se bloque pasLe thread d'interface ne s'occupe que de l'entrée, de l'affichage de progression et de l'acceptation de l'annulation ; un thread worker exécute le travail lourd et renvoie la fin au thread d'interface via PostMessage ou une continuation awaitpasser le travailPostMessage / continuation awaitThread d'interface : entrée, progression, annulationThread worker : travail lourdPas d'E/S synchrones ni de long calcul sur le thread d'interface

Figure 3 : passer le calcul lourd et le travail synchrone au worker, et ramener les mises à jour d’écran au thread d’interface. Les E/S asynchrones s’attendent avec une API asynchrone, comme en section 5.1.

L’attente du worker elle-même se écrit selon la discipline traitée dans l’article sur les variables de condition. Il est aussi important que le thread d’interface ne se remette pas à attendre le worker de façon synchrone.

5.4 Concevoir dès le départ la progression et l’annulation

Même si l’écran ne gèle pas, si rien ne change pendant longtemps, l’utilisateur ne peut pas savoir si ça travaille encore. Donc concevez l’affichage de progression et l’acceptation de l’annulation comme une partie du traitement.

Sens de la communication Mécanisme Rôle
Worker → interface IProgress<T> Signaler l’avancement à l’écran
Interface → worker CancellationToken Transmettre la demande d’arrêt

Le worker s’interrompt à un point de coupure raisonnable et fait le ménage. Disposer de moyens de progression et d’annulation réduit les situations où l’utilisateur recourt à une fin forcée. Une fin forcée peut entraîner une corruption de données.

6. Deux méthodes qui ressemblent à des contournements, et leurs limites

6.1 DoEvents fait entrer d’autres événements au milieu du traitement

Insérer Application.DoEvents() ou une boucle PeekMessage pendant un travail lourd traite les messages et évite l’affichage « Ne répond pas ». Cependant, un autre gestionnaire d’événement s’exécute alors alors que le traitement d’origine n’est pas encore terminé. C’est de la réentrance.

Par exemple, DoEvents est appelé au milieu d’une transformation de données, et un second clic de bouton déjà en file est traité. Ce gestionnaire réécrit les mêmes données, puis le traitement d’origine reprend. Dans cet ordre, l’état que le traitement d’origine supposait est déjà perdu.

Ce n’est pas seulement les boutons qui réentrent. La fermeture du formulaire et les minuteurs arrivent aussi. Des bugs comme des données en cours de traitement corrompues, ou une exception en touchant un formulaire fermé, dépendent du moment, se reproduisent mal, et tendent à être plus difficiles à investiguer que « Ne répond pas ».

Le principe est de séparer le travail, pas de pomper la boucle à la main. Le traitement manuel des messages reste confiné à des structures limitées, comme une boîte de dialogue de progression modale. Pour un travail long ordinaire, utilisez la séparation et la prévention de réentrance du chapitre 5.

6.2 Désactiver les fenêtres fantômes ne ramène pas le contrôle

DisableProcessWindowsGhosting est une API qui désactive le remplacement par une fenêtre fantôme pour le processus appelant. La désactivation dure pendant la durée de vie du processus.4

Elle existe pour des usages particuliers, comme un terminal kiosque, où l’on veut éviter que l’OS mette une fenêtre de substitution opérable. Le fait que le traitement des messages soit arrêté ne change pas, et l’utilisateur perd aussi le moyen de déplacer ou de quitter que la fenêtre fantôme offrait. Ce n’est pas un outil à utiliser comme contre-mesure « Ne répond pas » dans une application ordinaire.

Méthode Ce qui change Ce qui reste
Pomper à la main avec DoEvents ou analogue Traiter d’autres messages au milieu du travail Des événements arbitraires réentrent et peuvent casser l’état
DisableProcessWindowsGhosting Empêche l’OS de substituer une fenêtre de remplacement Le thread d’interface reste bouché
Séparer le travail long de l’interface Permet au thread d’interface de revenir à l’entrée et au dessin Il faut aussi concevoir la progression, l’annulation et la prévention de réentrance

7. Procédure d’investigation : conserver le moment du blocage avant de fermer

7.1 Prendre d’abord un dump

Dans l’investigation de « ça se bloque de temps en temps », le plus précieux est l’état des threads au moment même du blocage. Une fois que vous quittez ou redémarrez, cet état est perdu.

Dans l’onglet « Détails » du Gestionnaire des tâches, cliquez avec le bouton droit sur le processus cible et choisissez « Créer un fichier de vidage ». Vous obtenez un dump complet contenant les piles de tous les threads. Partagez aussi la procédure avec ceux qui reçoivent les tickets : « s’il se bloque, prenez un dump avant de fermer ».

Pour construire un mécanisme de collecte, voir l’article sur la collecte des dumps de crash.

7.2 Regarder le thread propriétaire de la fenêtre bloquée

Ouvrez le dump dans WinDbg et vérifiez la pile du thread d’interface. Le thread qui fait tourner la boucle de messages est en général le thread 0, mais certaines applications ont plusieurs threads d’interface, donc ne décidez pas seulement d’après le numéro ; regardez le thread propriétaire de la fenêtre bloquée.

Ce qui apparaît sur la pile Ce qu’il faut examiner ensuite
Attente dans ReadFile ou une API réseau Accès fichier, E/S synchrones, attente d’une réponse réseau
Attente dans une fonction WaitFor… Le verrou ou l’objet de synchronisation. Quelle fin il attend, et ce que fait l’autre partie
Attente à l’intérieur de SendMessage Si le thread destinataire peut traiter les messages. S’ils s’attendent mutuellement

La pile du thread d’interface montre ce sur quoi il attend. Si un interblocage est soupçonné, faites correspondre les cibles d’attente des deux threads, pas d’un seul. La façon de les lire est expliquée dans l’article d’introduction à WinDbg.

Procédure de base pour investiguer Ne répond pasPrendre un dump au moment du blocage, regarder la pile du thread d'interface, identifier s'il est arrêté sur des E/S synchrones, une attente de verrou ou un SendMessage entre threads, et relier cela à la correction de conception correspondanteLe 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 l'endroit concerné vers un worker

Figure 4 : à partir du dump du moment du blocage, suivre ce sur quoi le thread d’interface attend. Pour les E/S, asynchroniser ou séparer ; pour les verrous et SendMessage, vérifier aussi la structure d’attente.

7.3 Distinguer l’inspection en direct et l’analyse dans le temps

Outre les dumps, il y a un moyen de regarder un processus vivant sur place, et un moyen d’enregistrer le déroulement du temps.

Ce que vous voulez savoir Méthode
Conserver le moment du blocage et l’examiner plus tard Prendre un dump et l’analyser dans WinDbg
Voir sur place les threads et piles d’un processus vivant Utiliser Process Explorer
Suivre dans le temps une lenteur constante ou les attentes du thread d’interface Capturer une trace avec WPR et l’analyser dans WPA

La façon de prendre une série temporelle est traitée dans WPR/WPA en pratique.

7.4 Resserrer les candidats à partir du symptôme, puis confirmer avec des preuves

Les conditions de reproduction sont aussi une entrée dans l’investigation. Ne décidez toutefois pas la cause d’après le seul symptôme ; croisez-la avec les dumps et les traces.

Symptôme Soupçonner d’abord Où vérifier
Se bloque toujours sur une opération particulière E/S synchrones ou analogue dans ce gestionnaire Le traitement correspondant à l’opération et la pile du thread d’interface
Se bloque rarement, sans corrélation avec une opération Ordre des verrous, ou interblocage SendMessage entre threads Les piles des deux threads qui s’attendent
Ne se bloque que dans un environnement particulier Attentes et délais dus à un lecteur réseau, un proxy, un antivirus, etc. L’API en attente et son temps de réponse dans cet environnement

8. Résumé

« Ne répond pas » est un mécanisme dans lequel Windows juge que le traitement des messages d’une fenêtre s’est arrêté et met un écran de substitution. L’unité de jugement est la fenêtre et le thread GUI propriétaire, pas le processus entier. Le délai interne de 5 secondes est une valeur qui peut changer à l’avenir, et se distingue du temps de réponse confortable d’une interface.12

Ce qu’il faut corriger n’est pas l’affichage, mais le calcul ou l’attente qui occupe longtemps le thread d’interface. Le travail CPU et les API uniquement synchrones vont vers un worker, les E/S asynchrones vers des API asynchrones, et les mises à jour d’écran reviennent au thread d’interface. Concevez ensemble la progression, l’annulation et la prévention de réentrance. Contourner avec DoEvents ou désactiver les fenêtres fantômes n’en est pas un substitut.

Lorsque la cause est inconnue, commencez par prendre un dump avant de quitter et regarder ce sur quoi attend le thread propriétaire de la fenêtre bloquée. Remplacez « on dirait que c’est cassé » par « le thread d’interface ne peut pas revenir au traitement des messages », et les lieux d’investigation et les corrections de conception se relient.

Articles connexes

Domaines de conseil associés

KomuraSoft LLC prend en charge l’investigation des causes (analyse de dump et analyse de traces) d’applications métier qui « se bloquent de temps en temps » ou passent à « Ne répond pas », le refactoring de code d’interface héritée plein de traitements synchrones 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 la procédure de reproduction n’est pas encore connue, 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 son traitement de démarrage, et n’a pas appelé PeekMessage pendant le délai 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 ↩3 ↩4

  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 replace 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 ↩4 ↩5

  3. 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 ↩3 ↩4

  4. 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. ↩ ↩2

  5. 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

  6. 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. ↩

  7. 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 ↩4

  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 comme ne répondant pas lorsqu'une application fenêtrée n'attend pas d'entrée, n'est pas dans son traitement de démarrage, et n'a pas récupéré de message (PeekMessage) pendant 5 secondes. La fenêtre de premier niveau ainsi jugé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 permet que de 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 à sa place.
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 non-réponse, mais n'importe quel gestionnaire d'événement peut alors réentrer : un second clic du bouton, fermer la fenêtre, un minuteur, etc. Qu'un autre gestionnaire réécrive des données encore en cours de traitement, ou touche un formulaire censé être déjà fermé et lève une exception, produit des bugs de réentrance dépendants du moment et difficiles à reproduire — plus gênants 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'écran (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 envoie avec PostMessage un message de fin personnalisé au thread d'interface, et que la procédure de fenêtre mette à jour l'écran.
Comment investiguer la cause d'une application qui affiche « Ne répond pas » ?
L'important est de capturer l'état au moment 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, utilisez la liste de threads et la vue de pile de Process Explorer ; pour le suivre dans le temps, capturez une trace avec WPR. 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