WPF レンダリング スレッドエラーのトラブルシューティング

この記事では、Windows Presentation Foundation (WPF) レンダリング スレッドでのエラーについて説明します。 この記事では、 SyncFlush または NotifyPartitionIsZombie で発生する例外と、 WaitForNextMessage または SynchronizeChannelで発生するハング状況に焦点を当てます。

WPF アプリケーションには、独自のメッセージ ポンプ (Dispatcher.Run) を実行している 1 つ以上の UI スレッドがある場合があります。 各 UI スレッドは、スレッドのメッセージ キューからのウィンドウ メッセージを処理し、スレッドが所有するウィンドウにディスパッチする役割を担います。 各 WPF アプリケーションには、レンダリング スレッドが 1 つだけ含まれます。 この個別のスレッドは、Microsoft DirectX D3D (ソフトウェア レンダリング パイプラインが使用されている場合は GDI) と通信します。 WPF コンテンツの場合、各 UI スレッドは描画する内容に関する詳細な命令をレンダリング スレッドに送信します。 その後、レンダリング スレッドは、これらの指示に従ってコンテンツをレンダリングします。

適用対象: .NET Framework 4.8

SyncFlush、WaitForNextMessage、SynchronizeChannel、NotifyPartitionIsZombie でのエラー

開発者は、WPF アプリケーションで発生するスレッドエラーのレンダリングに関連する問題が発生することがよくあります。 ユーザーは、アプリケーションが次のような例外をスローすることを報告する場合があります。

  • System.Runtime.InteropServices.COMException: UCEERR_RENDERTHREADFAILURE (HRESULT からの例外: 0x88980406)
  • System.InvalidOperationException: レンダリング スレッドで未指定のエラーが発生しました。
  • System.OutOfMemoryException: プログラムの実行を続行するためのメモリが不足しています。

関連する呼び出し履歴は、 SyncFlush または NotifyPartitionIsZombieから始まります。 例えば次が挙げられます。

   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)  

アプリケーションが WaitForNextMessage または SynchronizeChannel で応答を停止し、次のような呼び出し履歴を生成する場合があります。

   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

これらの呼び出し履歴は、レンダー スレッドでの障害の症状です。 これは、例外と呼び出し履歴が一般的であるため、診断に困難な問題です。 レンダー スレッドエラーは、根本原因に関係なく、ここに記載されている呼び出し履歴の 1 つ (または呼び出し履歴の小さなバリエーション) を生成します。 そのため、問題を診断したり、個別の非対応インシデントが同じ根本原因を持つ状況を認識したりすることが困難になる場合があります。

SyncFlush、WaitForNextMessage、SynchronizeChannel、NotifyPartitionIsZombie のエラーの原因

WPF レンダリング スレッドで致命的なエラーが発生した場合、UI スレッドでソフトウェアの応答が停止する例外と状況が発生します。 これらのエラーにはいくつかの原因が考えられますが、レンダリング スレッドはその情報を UI スレッドと共有しません。 これらのエラーは 1 つのルート バグや問題に関連していないため、特定の解決策はありません。

WPF のレンダリング スレッドは、DirectX D3D、User32、GDI32 などの別のコンポーネントへの呼び出しを行うときに、戻り値の成功または失敗をチェックします。 エラーが検出されると、WPF はレンダー パーティションを "ゾンビ" し、2 つのスレッドが同期されたときにエラーを UI スレッドに通知します。 レンダー スレッドは、受け取ったエラーを適切なマネージド例外にマップしようとします。 たとえば、メモリ不足の状態のために WPF レンダリング スレッドが失敗した場合、エラーは System.OutOfMemoryExceptionにマップされます。 この例外は UI スレッドに表示されます。 レンダリング スレッドは、少数の場所でのみ UI スレッドと同期します。 そのため、前のセクションで説明した呼び出し履歴は、通常、問題が実際に発生する場所ではなく、問題の症状に気付いた場所に表示されます。 最も一般的な同期は、ウィンドウの設定が更新される場所 (サイズ、位置など) や、UI スレッドがレンダリング スレッドからの "チャネル" メッセージを処理する場所で発生します。

仕様上、UI スレッド上の例外と呼び出し履歴は、問題の診断に役立つ有用なリソースではありません。 例外がスローされる時点までに、レンダー スレッドは既に障害発生時点を過ぎている。 レンダリング スレッドの重大な状態は、エラーが発生した場所と理由を理解するのに役立ちますが、既に失われています。 このような状況のため、WPF アプリケーションの作成者は、エラーが発生した理由や回避方法を把握できません。 例外と呼び出し履歴を分析する代わりに、事後ユーザー ダンプ ファイルで問題をデバッグします。 このメソッドは少しだけ便利ですが、レンダー スレッドは失敗した呼び出し履歴の循環バッファーを保持します。 独自のデバッガー拡張機能とプライベート デバッグ シンボルを使用してバッファーを内部的に再構築し、おおよその障害点を示すことができます。 ただし、障害発生時のローカル、スタック変数、ヒープ オブジェクトなど、重大な状態にはアクセスできません。 通常、アプリケーションをもう一度実行して、関係していると思われる呼び出しでエラーを探します。

ビデオ ハードウェアまたはドライバーの障害

WPF レンダリング スレッドエラーの最も一般的なバケットは、ビデオ ハードウェアまたはドライバーの問題に関連しています。 WPF が DirectX を介してビデオ ドライバーに機能を照会すると、ドライバーがその機能を誤って報告する可能性があります。 このアクションにより、WPF は、DirectX D3D エラーの原因となるコード パスを取得します。 ドライバーが正しく実装されていない可能性もあります。 ほとんどのレンダリング スレッドエラーは、WPF がドライバーの欠陥を公開する方法でハードウェア レンダリング パイプラインを使用しようとするためです。 この状態は、最新のグラフィックス デバイスとドライバーを使用する最新バージョンの Windows で発生する可能性がありますが、一般的には WPF の初期に発生した場合と同様です。 このため、レンダー スレッドエラーのテストまたは回避を開始するときは、まず WPF でハードウェア アクセラレーションを無効にすることをお勧めします。

また、ドライバー (または DirectX) がレンダリングするには複雑すぎるシーンをアプリが要求した場合にも、エラーが発生する可能性があります。 この状況は、最新のドライバーでは一般的ではありません。 ただし、すべてのデバイスには、超過できる制限があります。

レンダリング スレッドエラーのもう 1 つの履歴ソースは、WPF の Window.AllowsTransparency プロパティまたは Popup.AllowsTransparency プロパティです。 これらのプロパティにより、 階層化されたウィンドウ が使用されます。 階層化されたウィンドウは、以前のバージョンの Windows で問題を引き起こしました。 ただし、これらの問題のほとんどは、Windows Vista のデスクトップ ウィンドウ マネージャー (DWM) の導入によって解決されました。

レンダリング スレッドエラーが System.OutOfMemoryExceptionとして現れる場合、通常、そのエラーはプロセスがリソースを使い果たしたことを示します。 この状況では、レンダリング スレッドがリソースの割り当てに失敗した Win32/DX API に呼び出されました。 WPF は、 E_OUTOFMEMORYERROR_NOT_ENOUGH_MEMORY などの戻り値を System.OutOfMemoryExceptionにマップします。 例外エントリは "メモリ" を参照しますが、このメンションは GDI オブジェクト ハンドル、他のシステム ハンドル、GPU メモリ、標準 RAM メモリなど、あらゆる種類のリソースを参照できます。

リソース割り当てエラーに関する解説

次の注釈は、 System.OutOfMemoryException エラーとリソース割り当てエラーに適用されます。

  • 根本原因は、エラーが発生したコードに関連していない可能性があります。 プロセス内の他のコードは、リソースを過剰に消費し、それ以外の場合は正常に実行されるコードには何も残さない可能性があります。

  • 要求が異常に大きい場合は、リソースが豊富であるように見えるにもかかわらず、エラーが発生する可能性があります。 大量の(連続した)メモリに対する要求は、システムに十分なメモリがある場合でも、System.OutOfMemoryException エラーを引き起こす可能性があります。 実際の例を次に示します。Visual Studio アドインは、前のセッションで保存された状態からウィンドウを復元する準備をします。 アドインは、前のモニターと現在のモニターの間の DPI の違いを正しく調整しません。 このエラーは、WPF、WindowsForms、VS ウィンドウ ホスティング コンポーネントの複数のレイヤーからの調整によって複雑になるため、アドインはウィンドウ サイズを適切なサイズの 16 倍に設定します。 その後、レンダー スレッドは、必要な 256 倍の大きいバック バッファーを割り当てようとします。 そのため、予想される割り当てに十分なメモリがある場合でも、プロセスは失敗します。

一般的な推奨事項

  1. ハードウェア レンダリングを無効にします。 「ハードウェア アクセラレーションの無効化オプション」で説明されている DisableHWAcceleration レジストリ値を使用します。 このアクションは、コンピューター上のすべての WPF アプリケーションに影響します。 この手順は、問題がグラフィックス ハードウェアまたはドライバーに関連しているかどうかをテストする場合にのみ実行します。 その場合は、より詳細なレベルでハードウェア アクセラレーションをプログラムで無効にすることで、問題を回避できます。 この手順は、ウィンドウごとにHwndTarget.RenderMode プロパティを使用するか、RenderOptions.ProcessRenderMode プロパティを使用してプロセスごとに実行できます。

  2. ビデオドライバを更新するか、問題のあるコンピュータ上で別のビデオハードウェアを試してください。

  3. ターゲット プラットフォームで使用できる Microsoft .NET Framework の最新バージョンとサービス パック レベルにアップグレードします。

  4. 最新のオペレーティング システムにアップグレードします。

  5. アプリケーションで Windows.AllowsTransparencyPopup.AllowsTransparency を使用する機能を無効にします。

  6. System.OutOfMemoryExceptionsが報告された場合は、パフォーマンス モニターでプロセス メモリの使用状況を監視します。 特に、すべてのヒープ カウンターで Process\Virtual Bytes、Process\Private Bytes、.NET CLR Memory\# Bytes を監視します。 また、Windows タスク マネージャーでプロセスのユーザー オブジェクトと GDI オブジェクトを監視します。 特定のリソースが使い果たされていると判断した場合は、アプリケーションのトラブルシューティングを行って、過剰なリソース消費を修正します。 ガイドラインとして、リソース割り当ての問題に関する前のセクションの 2 つの解説に従ってください。

  7. プラットフォーム間で、または異なるビデオ ハードウェアまたはドライバーの組み合わせで再現可能なシナリオがある場合は、WPF のバグが発生する可能性があります。 問題を Microsoft に報告する前に、調査を有効にするために十分な情報を収集していることを確認してください。 コールスタックだけでは不十分です。 次のようなより詳細な情報が必要です。

    • 環境の説明 (OS、.NET、グラフィックス) など、問題を再現する手順を含む完全な VS ソリューション。
    • 問題の Time-Travel デバッグ トレース
    • 完全なクラッシュ ダンプ ファイル。