Résoudre les problèmes liés aux échecs de thread de rendu WPF

Cet article traite des échecs dans le thread de rendu WPF (Windows Presentation Foundation). Cet article se concentre sur les exceptions qui se produisent dans SyncFlush ou NotifyPartitionIsZombie et sur les situations de blocage qui se produisent dans WaitForNextMessage ou SynchronizeChannel.

Les applications WPF peuvent avoir un ou plusieurs threads d’interface utilisateur qui exécutent leur propre pompe de messages (Dispatcher.Run). Chaque thread d’interface utilisateur est chargé de traiter les messages de fenêtre à partir de la file d’attente de messages du thread et de les distribuer à des fenêtres propriétaires du thread. Chaque application WPF n’a qu’un seul thread de rendu. Ce thread distinct communique avec Microsoft DirectX D3D (ou GDI, si le pipeline de rendu logiciel est utilisé). Pour le contenu WPF, chaque thread d’interface utilisateur envoie des instructions détaillées au thread de rendu sur ce qu’il faut dessiner. Le thread de rendu suit ensuite ces instructions pour afficher le contenu.

S’applique à : .NET Framework 4.8

Échecs dans SyncFlush, WaitForNextMessage, SynchronizeChannel et NotifyPartitionIsZombie

Les développeurs rencontrent souvent des problèmes liés aux échecs de thread de rendu qui se produisent dans les applications WPF. Les utilisateurs peuvent signaler que l'application génère une exception, par exemple :

  • System.Runtime.InteropServices.COMException : UCEERR_RENDERTHREADFAILURE (Exception de HRESULT : 0x88980406)
  • System.InvalidOperationException : une erreur non spécifiée s’est produite sur le thread de rendu.
  • System.OutOfMemoryException : mémoire insuffisante pour poursuivre l’exécution du programme.

La pile d’appels associée démarre à SyncFlush ou NotifyPartitionIsZombie. Par exemple :

   at System.Windows.Media.Composition.DUCE.Channel.SyncFlush()  
   at System.Windows.Interop.HwndTarget.UpdateWindowSettings(Boolean enableRenderTarget, Nullable\`1 channelSet)  
   at System.Windows.Interop.HwndTarget.UpdateWindowSettings(Boolean enableRenderTarget)  
   at System.Windows.Interop.HwndTarget.UpdateWindowPos(IntPtr lParam)  
   at System.Windows.Interop.HwndTarget.HandleMessage(WindowMessage msg, IntPtr wparam, IntPtr lparam)  
   at System.Windows.Media.MediaContext.NotifyPartitionIsZombie(Int32 failureCode)  
   at System.Windows.Media.MediaContext.NotifyChannelMessage()  
   at System.Windows.Interop.HwndTarget.HandleMessage(Int32 msg, IntPtr wparam, IntPtr lparam)  

L'application peut cesser de répondre dans WaitForNextMessage ou SynchronizeChannel et générer une pile d'appels, par exemple :

   ntdll.dll!NtWaitForMultipleObjects
   kernelbase.dll!WaitForMultipleObjectsEx
   kernelbase.dll!WaitForMultipleObjects
   wpfgfx_v0400.dll!CMilChannel::WaitForNextMessage
   wpfgfx_v0400.dll!MilComposition_WaitForNextMessage
   presentationcore.dll!System.Windows.Media.MediaContext.CompleteRender
   kernelbase.dll!WaitForSingleObject
   wpfgfx_v0400.dll!CMilConnection::SynchronizeChannel
   wpfgfx_v0400.dll!CMilChannel::SyncFlush
   presentationcore.dll!System.Windows.Media.Composition.DUCE+Channel.SyncFlush
   presentationcore.dll!System.Windows.Media.MediaContext.CompleteRender
   presentationcore.dll!System.Windows.Interop.HwndTarget.OnResize
   presentationcore.dll!System.Windows.Interop.HwndTarget.HandleMessage

Ces piles d’appels sont des symptômes d’une défaillance dans le thread de rendu. Il s’agit d’un problème difficile à diagnostiquer, car les exceptions et les piles d’appels sont génériques. Les échecs du thread de rendu génèrent l’une des piles d’appels répertoriées ici (ou une légère variation de celles-ci), quelle que soit la cause fondamentale. Par conséquent, il peut être difficile de diagnostiquer le problème ou de reconnaître une situation dans laquelle des incidents distincts de non-réponse ont la même cause racine.

Causes des échecs dans SyncFlush, WaitForNextMessage, SynchronizeChannel et NotifyPartitionIsZombie

Les exceptions et les situations dans lesquelles le logiciel cesse de répondre se produisent dans un thread d’interface utilisateur si le thread de rendu WPF rencontre une erreur irrécupérable. Ces erreurs ont plusieurs causes possibles, mais le thread de rendu ne partage pas ces informations avec le thread d’interface utilisateur. Étant donné que ces erreurs ne sont pas liées à un seul bogue ou problème racine, elles n’ont aucune solution spécifique.

Le thread de rendu de WPF vérifie la valeur de retour en cas de réussite ou d’échec lorsqu’il effectue un appel dans un autre composant, tel que DirectX D3D, User32 ou GDI32. Lorsqu’un échec est détecté, WPF « zombies » la partition de rendu et avertit le thread d’interface utilisateur de l’échec lorsque les deux threads sont synchronisés. Le thread de rendu tente de mapper l’échec qu’il reçoit à une exception managée appropriée. Par exemple, si le thread de rendu WPF a échoué en raison d’une condition de mémoire insuffisante, il mappe l’échec à un System.OutOfMemoryException. Cette exception s’affiche sur le thread d’interface utilisateur. Le thread de rendu se synchronise avec le thread d’interface utilisateur à quelques emplacements seulement. Par conséquent, les piles d’appels mentionnées dans la section précédente apparaissent généralement où vous remarquez des symptômes du problème, et non pas là où le problème se produit réellement. La synchronisation se produit généralement dans des emplacements où les paramètres d’une fenêtre sont mis à jour (taille, position, et ainsi de suite) ou où le thread d’interface utilisateur gère un message « canal » à partir du thread de rendu.

De par sa conception, les exceptions et les piles d’appels sur le thread d’interface utilisateur ne sont pas des ressources utiles pour vous aider à diagnostiquer le problème. Au moment où l’exception est levée, le thread de rendu dépasse déjà le point de défaillance. L'état critique du thread de rendu aurait pu vous aider à comprendre où et pourquoi l'échec s'est produit, mais il est déjà perdu. En raison de cette situation, l’auteur d’une application WPF ne peut pas savoir pourquoi l’échec s’est produit ou comment l’éviter. Au lieu d’analyser les exceptions et les piles d’appels, nous déboguons le problème dans un fichier de vidage de processus après défaillance. Bien que cette méthode ne soit que légèrement plus utile, le thread de rendu conserve une mémoire tampon circulaire de la pile d’appels défaillante. Nous pouvons reconstruire la mémoire tampon en interne à l’aide d’une extension de débogueur propriétaire et de symboles de débogage privé pour afficher le point de défaillance initial approximatif. Toutefois, nous n’avons pas accès à l’état critique, comme les variables locales, les variables de pile et les objets du tas au moment de l’échec. Nous exécutons à nouveau l'application pour identifier les problèmes sur les appels que nous soupçonnons être impliqués.

Pannes de matériel vidéo ou de pilotes vidéo

Le compartiment le plus courant des échecs de thread de rendu WPF est associé à des problèmes de matériel vidéo ou de pilote. Lorsque WPF interroge le pilote vidéo pour obtenir des fonctionnalités via DirectX, le pilote peut mal signaler ses fonctionnalités. Cette action entraîne l’exécution d’un chemin de code WPF qui provoque des erreurs DirectX D3D. Le pilote peut également être implémenté de manière incorrecte. La plupart des échecs de thread de rendu se produisent parce que WPF tente d’utiliser le pipeline de rendu matériel de manière à exposer certaines failles dans le pilote. Cette condition peut se produire sur les versions modernes de Windows qui utilisent des périphériques graphiques et des pilotes modernes, bien qu’elles se produisent aussi souvent que dans les premiers jours de WPF. Pour cette raison, lorsque vous commencez à tester ou à contourner un échec de thread de rendu, nous vous recommandons de désactiver d’abord l’accélération matérielle dans WPF.

Une défaillance peut également se produire si une application demande une scène trop complexe à produire pour le pilote (ou DirectX). Cette situation n’est pas courante pour les pilotes modernes. Toutefois, chaque appareil a des limites qui peuvent être dépassées.

Une autre source historique d’échecs de thread de rendu est les propriétés Window.AllowsTransparency ou Popup.AllowsTransparency dans WPF. Ces propriétés entraînent l’utilisation de fenêtres superposées . Les fenêtres en couches ont provoqué des problèmes dans les versions antérieures de Windows. Toutefois, la plupart de ces problèmes ont été résolus par l’introduction du Gestionnaire de fenêtres de bureau (DWM) dans Windows Vista.

Si un échec du thread de rendu se manifeste par un System.OutOfMemoryException, cette erreur suggère généralement que le processus a épuisé certaines ressources. Dans ce cas, le thread de rendu a fait appel à une API qui a tenté d’allouer une Win32/DX ressource, mais cela a échoué. WPF mappe les valeurs de retour telles que E_OUTOFMEMORY ou ERROR_NOT_ENOUGH_MEMORY à un System.OutOfMemoryException. Bien que l’entrée d’exception fasse référence à « mémoire », cette mention peut faire référence à n’importe quel type de ressource, comme les handles d’objets GDI, d’autres handles système, la mémoire GPU, la mémoire RAM standard, etc.

Remarques sur les échecs d’allocation de ressources

Les remarques suivantes s’appliquent aux System.OutOfMemoryException défaillances et à toute défaillance d’allocation de ressources :

  • La cause racine peut ne pas être liée au code qui rencontre l’échec. D’autres codes du processus peuvent sur-consommer la ressource et ne laisser aucun code qui s’exécuterait autrement correctement.

  • Si la demande est inhabituellement importante, l’échec peut se produire malgré une ressource qui semble être abondante. Une demande d'une grande quantité de mémoire (contiguë) peut causer un System.OutOfMemoryException, même si le système a beaucoup de mémoire. Voici un exemple réel : un complément Visual Studio se prépare à restaurer sa fenêtre à partir d’un état enregistré dans une session précédente. Le complément s’ajuste incorrectement en fonction de la différence de DPI entre les moniteurs précédents et actuels. Étant donné que cette erreur est due à des ajustements de plusieurs couches de composants WPF, Windows Forms et VS, le complément définit sa fenêtre à une taille 16 fois supérieure à la correcte. Ensuite, le thread de rendu tente d’allouer une mémoire tampon back-buffer de 256 fois plus grande que nécessaire. Par conséquent, le processus échoue même s’il existe suffisamment de mémoire disponible pour l’allocation attendue.

Recommandations générales

  1. Désactivez le rendu matériel. Utilisez la valeur de Registre DisableHWAcceleration décrite dans l’option Désactiver l’accélération matérielle. Cette action affecte toutes les applications WPF sur votre ordinateur. Effectuez cette étape uniquement pour tester si votre problème est lié au matériel graphique ou aux pilotes. Si c’est le cas, vous pouvez contourner le problème en désactivant par programme l’accélération matérielle à un niveau plus précis. Cette étape peut être effectuée par fenêtre à l’aide de la propriété HwndTarget.RenderMode ou par processus à l’aide de la propriété RenderOptions.ProcessRenderMode .

  2. Mettez à jour vos pilotes vidéo ou essayez un matériel vidéo différent sur les ordinateurs problématiques.

  3. Effectuez une mise à niveau vers la dernière version et le niveau de Service Pack de Microsoft .NET Framework qui sont disponibles pour votre plateforme cible.

  4. Effectuez une mise à niveau vers le système d’exploitation le plus récent.

  5. Désactivez la possibilité d’utiliser Windows.AllowsTransparency et Popup.AllowsTransparency dans votre application.

  6. Si System.OutOfMemoryExceptions sont signalées, surveillez l’utilisation de la mémoire du processus dans le Moniteur de performance. En particulier, surveillez les octets Process\Virtual Bytes, Process\Private Bytes et .NET CLR Memory\# dans tous les compteurs heaps. Surveillez également les objets utilisateur et les objets GDI pour le processus dans le Gestionnaire des tâches Windows. Si vous déterminez qu’une ressource spécifique est épuisée, résolvez les problèmes d’application pour corriger la consommation excessive de ressources. Pour obtenir des instructions, suivez les deux remarques de la section précédente sur les problèmes d’allocation de ressources.

  7. Si vous avez un scénario reproductible qui se produit sur plusieurs plateformes ou dans différentes combinaisons de matériel vidéo ou de pilote, vous pouvez avoir un bogue WPF. Veillez à collecter suffisamment d’informations pour activer une enquête avant de signaler le problème à Microsoft. Une pile d’appels n’est pas suffisante en soi. Nous avons besoin d’informations plus détaillées, telles que :

    • Solution VS complète qui inclut des étapes pour reproduire le problème, y compris une description de l’environnement (système d’exploitation, .NET et graphiques).
    • Trace de débogage de voyage dans le temps du problème.
    • Un fichier de vidage mémoire complet.