Fehlerbehebung bei Ausfällen des WPF-Render-Threads

In diesem Artikel werden Fehler im WPF-Renderthread (Windows Presentation Foundation) erläutert. Dieser Artikel konzentriert sich auf Ausnahmen, die in SyncFlush oder NotifyPartitionIsZombie auftreten, und auf Hangsituationen, die in WaitForNextMessage oder SynchronizeChannel auftreten.

WPF-Anwendungen verfügen möglicherweise über einen oder mehrere UI-Threads, die eine eigene Nachrichtenpumpe (Dispatcher.Run) ausführen. Jeder UI-Thread ist für die Verarbeitung von Fenstermeldungen aus der Nachrichtenwarteschlange des Threads und das Verteilen an Fenster verantwortlich, die der Thread besitzt. Jede WPF-Anwendung verfügt nur über einen Renderthread. Dieser separate Thread kommuniziert mit Microsoft DirectX D3D (oder GDI, wenn die Softwarerenderingpipeline verwendet wird). Für WPF-Inhalte sendet jeder UI-Thread detaillierte Anweisungen an den Renderthread, was gezeichnet werden soll. Der Renderthread folgt dann diesen Anweisungen zum Rendern des Inhalts.

Gilt für: .NET Framework 4.8

Fehler in SyncFlush, WaitForNextMessage, SynchronizeChannel und NotifyPartitionIsZombie

Entwickler haben häufig Probleme im Zusammenhang mit Render-Thread-Fehlern, die in WPF-Anwendungen auftreten. Benutzer können melden, dass ihre Anwendung eine Ausnahme auslöst, z. B.:

  • System.Runtime.InteropServices.COMException: UCEERR_RENDERTHREADFAILURE (Ausnahme von HRESULT: 0x88980406)
  • System.InvalidOperationException: Im Renderthread ist ein nicht angegebener Fehler aufgetreten.
  • System.OutOfMemoryException: Unzureichender Arbeitsspeicher, um die Ausführung des Programms fortzusetzen.

Der zugehörige Aufrufstapel beginnt bei SyncFlush oder NotifyPartitionIsZombie. Zum Beispiel:

   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)  

Die Anwendung reagiert möglicherweise in WaitForNextMessage oder SynchronizeChannel nicht mehr und generiert einen Aufrufstapel, z. B.:

   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

Diese Aufrufstapel sind Symptome eines Fehlers im Renderthread. Dies ist ein schwieriges Problem zu diagnostizieren, da die Ausnahmen und Aufrufstapel generisch sind. Renderthreadfehler generieren einen der Aufrufstapel, die hier aufgelistet sind (oder eine kleinere Variation der Aufrufstapel), unabhängig von der Ursache. Daher kann es schwierig sein, das Problem zu diagnostizieren oder eine Situation zu erkennen, in der separate Vorfälle von Nichtantworten die gleiche Ursache haben.

Ursachen der Fehler in SyncFlush, WaitForNextMessage, SynchronizeChannel und NotifyPartitionIsZombie

Ausnahmen und Situationen, in denen die Software nicht mehr reagiert, treten in einem UI-Thread auf, wenn der WPF-Renderthread einen schwerwiegenden Fehler verursacht. Diese Fehler haben mehrere mögliche Ursachen, aber der Renderthread teilt diese Informationen nicht zusammen mit dem UI-Thread. Da diese Fehler nicht mit einem einzigen Stammfehler oder -problem zusammenhängen, gibt es keine spezifische Lösung.

Der Renderthread von WPF überprüft den Rückgabewert auf Erfolg oder Fehler, wenn ein Aufruf an eine andere Komponente ausgeführt wird, z. B. DirectX D3D, User32 oder GDI32. Wenn ein Fehler erkannt wird, isoliert WPF die Render-Partition und benachrichtigt den UI-Thread des Fehlers, wenn die beiden Threads synchronisiert werden. Der Renderthread versucht, den Fehler zuzuordnen, den er an eine entsprechende verwaltete Ausnahme empfängt. Wenn beispielsweise der WPF-Renderthread aufgrund eines Speichermangelzustands fehlschlägt, wird der Fehler einem System.OutOfMemoryException zugeordnet. Diese Ausnahme wird im UI-Thread angezeigt. Der Renderthread wird nur an wenigen Stellen mit dem UI-Thread synchronisiert. Daher werden die im vorherigen Abschnitt erwähnten Aufrufstapel in der Regel angezeigt, wenn Sie Symptome des Problems bemerken, nicht wo das Problem tatsächlich auftritt. Die Synchronisierung erfolgt am häufigsten an Orten, an denen die Einstellungen eines Fensters aktualisiert werden (Größe, Position usw.) oder wo der UI-Thread eine Kanalnachricht aus dem Renderthread verarbeitet.

Standardmäßig sind die Ausnahmen und Aufrufstapel im UI-Thread keine nützlichen Ressourcen, um das Problem zu diagnostizieren. Wenn die Ausnahme ausgelöst wird, liegt der Renderthread bereits über dem Fehlerpunkt. Der kritische Zustand des Renderthreads würde Ihnen helfen zu verstehen, wo und warum der Fehler aufgetreten ist, aber er ist bereits verloren. Aus diesem Grund kann der Autor einer WPF-Anwendung nicht wissen, warum der Fehler aufgetreten ist oder wie sie vermieden werden kann. Anstatt Ausnahmen und Aufrufstapel zu analysieren, debuggen wir das Problem in einer Postmortem-Benutzerabbilddatei. Obwohl diese Methode nur etwas nützlicher ist, behält der Renderthread einen kreisförmigen Puffer des fehlerhaften Aufrufstapels bei. Wir können den Puffer intern rekonstruieren, indem wir eine proprietäre Debuggererweiterung und private Debugsymbole verwenden, um den ungefähren Anfangspunkt des Fehlers anzuzeigen. Wir haben jedoch keinen Zugriff auf den kritischen Zustand, z. B. lokale Variablen, Stack-Variablen und Heap-Objekte beim Auftreten des Fehlers. In der Regel führen wir die Anwendung erneut aus, um nach Fehlern bei den Anrufen zu suchen, die wir vermuten.

Fehler mit Videohardware oder Treibern

Der häufigste Bucket von WPF-Renderthreadfehlern ist mit Videohardware- oder Treiberproblemen verbunden. Wenn WPF den Videotreiber über DirectX auf Funktionen abfragt, kann der Treiber seine Funktionen falsch berichten. Diese Aktion bewirkt, dass WPF einen Codepfad verwendet, der einige DirectX D3D-Fehler verursacht. Der Treiber kann auch falsch implementiert werden. Die meisten Renderthread-Fehler treten auf, weil WPF versucht, die Hardwarerendering-Pipeline so zu nutzen, dass dadurch einige Schwächen im Treiber aufgedeckt werden. Diese Bedingung kann in modernen Versionen von Windows auftreten, die moderne Grafikgeräte und Treiber verwenden, obwohl sie in den frühen Tagen von WPF so häufig aufgetreten sind. Aus diesem Grund wird empfohlen, die Hardwarebeschleunigung in WPF zuerst zu deaktivieren, wenn Sie mit dem Testen oder Umgehen eines Renderthreadfehlers beginnen.

Ein Fehler kann auch auftreten, wenn eine App eine Szene anfordert, die für das Rendern des Treibers (oder DirectX) zu komplex ist. Diese Situation ist für moderne Treiber nicht üblich. Jedes Gerät verfügt jedoch über Grenzwerte, die überschritten werden können.

Eine weitere historische Quelle von Renderthreadfehlern sind die Window.AllowsTransparency - oder Popup.AllowsTransparency-Eigenschaften in WPF. Diese Eigenschaften führen dazu, dass mehrschichtige Fenster verwendet werden. Überschichtete Fenster verursachten Probleme in älteren Versionen von Windows. Die meisten dieser Probleme wurden jedoch durch die Einführung des Desktop Window Manager (DWM) in Windows Vista behoben.

Wenn sich ein Renderthreadfehler als manifestiert System.OutOfMemoryException, gibt dieser Fehler in der Regel an, dass der Prozess einige Ressourcen erschöpft hat. In diesem Fall wurde der Renderthread in eine Win32/DX-API aufgerufen, die erfolglos versucht hat, eine Ressource zuzuweisen. WPF ordnet Rückgabewerte wie E_OUTOFMEMORY oder ERROR_NOT_ENOUGH_MEMORY zu einem System.OutOfMemoryException. Obwohl der Ausnahmeeintrag auf "Speicher" verweist, kann diese Erwähnung auf jede Art von Ressource verweisen, z. B. GDI-Objekthandles, andere Systemhandles, GPU-Speicher, Standard-RAM-Speicher usw.

Hinweise zu Ressourcenzuordnungsfehlern

Die folgenden Hinweise gelten für System.OutOfMemoryException-Fehler und Ressourcenallokationsfehler.

  • Die Ursache ist möglicherweise nicht mit dem Code verknüpft, der den Fehler verursacht. Anderer Code im Prozess kann die Ressource überbeanspruchen und nichts für den Code belassen, der andernfalls erfolgreich ausgeführt würde.

  • Wenn die Anforderung ungewöhnlich groß ist, kann der Fehler trotz einer Ressource auftreten, die scheinbar reichlich vorhanden ist. Eine Anforderung nach einer großen Menge (zusammenhängenden) Speichers kann selbst dann ein System.OutOfMemoryException verursachen, wenn das System über reichlich Arbeitsspeicher verfügt. Hier ist ein beispiel für die Praxis: Ein Visual Studio-Add-In bereitet das Wiederherstellen des Fensters aus einem Zustand vor, der in einer vorherigen Sitzung gespeichert wurde. Das Add-In passt sich falsch an den Dpi-Unterschied zwischen den vorherigen und aktuellen Monitoren an. Da dieser Fehler durch Anpassungen aus mehreren Ebenen von WPF-, WindowsForms- und VS-Fensterhostingkomponenten zusammengesetzt wird, legt das Add-In seine Fenstergröße auf 16 Mal größer als die richtige Größe fest. Anschließend versucht der Renderthread, einen Hintergrundpuffer zuzuweisen, der 256 mal größer als erforderlich ist. Daher schlägt der Prozess fehl, obwohl genügend Arbeitsspeicher für die erwartete Zuordnung vorhanden ist.

Allgemeine Empfehlungen

  1. Hardwarerendering deaktivieren. Verwenden Sie den Registrierungswert DisableHWAcceleration , der in der Option "Hardwarebeschleunigung deaktivieren" erläutert wird. Diese Aktion wirkt sich auf alle WPF-Anwendungen auf Ihrem Computer aus. Führen Sie diesen Schritt nur aus, um zu testen, ob ihr Problem mit Grafikhardware oder Treibern zusammenhängt. Wenn dies der Grund ist, können Sie das Problem umgehen, indem Sie die Hardwarebeschleunigung programmgesteuert auf granularerer Ebene deaktivieren. Dieser Schritt kann pro Fenster mithilfe der HwndTarget.RenderMode-Eigenschaft oder pro Prozess mithilfe der RenderOptions.ProcessRenderMode-Eigenschaft erfolgen.

  2. Aktualisieren Sie Ihre Videotreiber, oder probieren Sie andere Videohardware auf den Problemcomputern aus.

  3. Führen Sie ein Upgrade auf die neueste Version und das Service Pack-Level von Microsoft .NET Framework durch, das für Ihre Zielplattform verfügbar ist.

  4. Führen Sie ein Upgrade auf das neueste Betriebssystem durch.

  5. Deaktivieren Sie die Möglichkeit zur Verwendung Windows.AllowsTransparency und Popup.AllowsTransparency in Ihrer Anwendung.

  6. Wenn System.OutOfMemoryExceptions gemeldet wird, überwachen Sie die Prozessspeicherauslastung im Leistungsmonitor. Überwachen Sie insbesondere die Zähler "Process\Virtual Bytes", "Process\Private Bytes" und ".NET CLR Memory\# Bytes" in allen Heaps-Zählern. Überwachen Sie außerdem die Benutzerobjekte und GDI-Objekte für den Prozess im Windows Task Manager. Wenn Sie feststellen, dass eine bestimmte Ressource erschöpft ist, beheben Sie die Anwendung, um den übermäßigen Ressourcenverbrauch zu beheben. Befolgen Sie als Richtlinie die beiden Anmerkungen im vorherigen Abschnitt zu Ressourcenzuordnungsproblemen.

  7. Wenn Sie über ein reproduzierbares Szenario verfügen, das plattformübergreifend oder in unterschiedlichen Videohardware- oder Treiberkombinationen auftritt, liegt möglicherweise ein WPF-Fehler vor. Stellen Sie sicher, dass Sie genügend Informationen sammeln, um eine Untersuchung zu aktivieren, bevor Sie das Problem an Microsoft melden. Ein Aufrufstapel allein reicht nicht aus. Wir benötigen detailliertere Informationen, z. B.:

    • Eine vollständige VS-Lösung, die Schritte zum Reproduzieren des Problems enthält, einschließlich einer Beschreibung der Umgebung (Betriebssystem, .NET und Grafiken).
    • Eine Zeitreisedebugging-Ablaufverfolgung des Problems.
    • Eine vollständige Absturzabbilddatei.