Поделиться через


Устранение неполадок потока рендеринга WPF

В этой статье рассматриваются сбои в потоке отрисовки Windows Presentation Foundation (WPF). В этой статье рассматриваются исключения, происходящие в SyncFlush или NotifyPartitionIsZombie, и ситуации зависания, происходящие в WaitForNextMessage или SynchronizeChannel.

Приложения WPF могут иметь один или несколько потоков пользовательского интерфейса, работающих под управлением собственного насоса сообщений (Dispatcher.Run). Каждый поток пользовательского интерфейса отвечает за обработку сообщений окна из очереди сообщений потока и их отправку в окна, принадлежащие потоку. Каждое приложение WPF имеет только один поток отрисовки. Этот отдельный поток взаимодействует с Microsoft DirectX D3D (или GDI, если используется конвейер отрисовки программного обеспечения). Для содержимого WPF каждый поток пользовательского интерфейса отправляет подробные инструкции в поток отрисовки, на который нужно нарисовать. Затем поток отрисовки следует этим инструкциям для отрисовки содержимого.

Область применения: .NET Framework 4.8

Сбои в SyncFlush, WaitForNextMessage, SyncChannel и 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

Эти стеки вызовов являются симптомами сбоя в потоке отрисовки. Это сложная проблема для диагностики, так как исключения и стеки вызовов являются общими. Сбои потока рендеринга создают один из стеков вызовов, перечисленных здесь (или его незначительные вариации) независимо от коренной причины. Таким образом, может быть трудно диагностировать проблему или признать ситуацию, в которой отдельные инциденты, не относящиеся к ответу, имеют одну и ту же первопричину.

Причины сбоев в SyncFlush, WaitForNextMessage, SynchronizeChannel и NotifyPartitionIsZombie

Исключения и ситуации, в которых программное обеспечение перестает отвечать в потоке пользовательского интерфейса WPF, происходят, если поток отрисовки WPF испытывает критическую ошибку. Эти ошибки имеют несколько возможных причин, но поток отрисовки не предоставляет общий доступ к этой информации вместе с потоком пользовательского интерфейса. Так как эти ошибки не связаны с одной корневой ошибкой или проблемой, у них нет конкретного решения.

Поток рендеринга WPF проверяет возвращаемое значение успешности или неудачи при вызове другого компонента, например DirectX D3D, User32 или GDI32. При обнаружении сбоя WPF делает недействительной секцию отрисовки и уведомляет поток пользовательского интерфейса о сбое, когда два потока синхронизированы. Поток отрисовки пытается сопоставить сбой, который он получает, с соответствующим управляемым исключением. Например, если поток отрисовки WPF завершился сбоем из-за нехватки памяти, он сопоставляет сбой с System.OutOfMemoryException. Это исключение отображается в потоке пользовательского интерфейса. Поток отрисовки синхронизируется с потоком пользовательского интерфейса только в нескольких точках. Поэтому стеки вызовов, упомянутые в предыдущем разделе, обычно появляются там, где вы заметили симптомы проблемы, а не там, где на самом деле возникает проблема. Синхронизация чаще всего происходит в местах, где обновляются параметры окна (размер, положение и т. д.), или где поток пользовательского интерфейса обрабатывает сообщение «channel» от потока рендеринга.

По задумке, исключения и стеки вызовов в потоке пользовательского интерфейса не являются полезными ресурсами для диагностики проблемы. Когда возникает исключение, поток отрисовки уже преодолел точку сбоя. Текущее состояние потока рендеринга поможет понять, где и почему произошла ошибка, но эта информация уже утеряна. Из-за этой ситуации автор приложения WPF не может знать, почему произошел сбой или как избежать его. Вместо анализа исключений и стеков вызовов мы отлаживаем проблему в дамп-файле пользователя после завершения работы. Хотя этот метод лишь немного более полезен, поток отрисовки сохраняет циклический буфер стека вызовов сбоя. Мы можем восстановить буфер внутренне с помощью закрытого расширения отладчика и частных символов отладки, чтобы показать приблизительную начальную точку сбоя. Однако у нас нет доступа к критическому состоянию системы, например локальным переменным стека и объектам кучи в момент сбоя. Как правило, мы снова запускаем приложение, чтобы искать ошибки в подозреваемых нами вызовах.

Сбои с видео оборудованием или драйверами

Наиболее распространенный сегмент ошибок потока отрисовки WPF связан с проблемами видеоустройства или драйвера. Когда WPF запрашивает видеодрайвер о возможностях через DirectX, драйвер может неправильно сообщить свои возможности. Это действие приводит к тому, что WPF принимает путь кода, вызывающий некоторые сбои DirectX D3D. Драйвер также может быть реализован неправильно. Большинство сбоев потока отрисовки происходят, потому что WPF пытается использовать аппаратный конвейер отрисовки таким образом, что выявляются некоторые недостатки в драйвере. Это условие может возникать в современных версиях Windows, использующих современные графические устройства и драйверы, но не так часто, как происходило в первые дни WPF. По этой причине при запуске тестирования или обхода сбоя потока отрисовки рекомендуется сначала отключить аппаратное ускорение в WPF.

Сбой также может произойти, если приложение запрашивает сцену, которая слишком сложна для отрисовки драйвера (или DirectX). Эта ситуация не является распространённой для современных водителей. Однако каждое устройство имеет ограничения, которые могут быть превышены.

Другим историческим источником сбоев потока отрисовки являются свойства Window.AllowsTransparency или Popup.AllowsTransparency в WPF. Эти свойства приводят к использованию многоуровневых окон . Многоуровневые окна вызвали проблемы в более ранних версиях Windows. Однако большинство этих проблем были решены введением диспетчера окон рабочего стола (DWM) в Windows Vista.

Если сбой потока отрисовки проявляется как System.OutOfMemoryException, эта ошибка обычно указывает на то, что процесс исчерпал какой-то ресурс. В этой ситуации поток отрисовки вызвал в Win32/DX API, который попытался безуспешно выделить некоторый ресурс. WPF сопоставляет возвращаемые значения, E_OUTOFMEMORY например или ERROR_NOT_ENOUGH_MEMORY с System.OutOfMemoryException. Хотя запись исключения относится к "памяти", это упоминание может ссылаться на любой тип ресурса, например дескриптор объектов GDI, другие системные дескрипторы, память GPU, стандартную память ОЗУ и т. д.

Примечания о сбоях выделения ресурсов

Следующие замечания относятся к сбоям и любому сбою в выделении ресурсов 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.AllowsTransparency и Popup.AllowsTransparency в приложении.

  6. Если System.OutOfMemoryExceptions сообщается, отслеживайте использование памяти процесса в Диспетчере производительности. В частности, следите за счетчиками Process\Virtual Bytes, Process\Private Bytes и .NET CLR Memory\# Bytes in All Heaps. Кроме того, отслеживайте пользовательские объекты и объекты GDI для процесса в диспетчере задач Windows. Если вы определите, что определенный ресурс исчерпан, устраните проблему с приложением для устранения чрезмерного потребления ресурсов. В качестве руководства следуйте двум замечаниям в предыдущем разделе о проблемах с выделением ресурсов.

  7. Если у вас есть воспроизводимый сценарий, который происходит на разных платформах или в разных сочетаниях видео или драйверов, может возникнуть ошибка WPF. Убедитесь, что вы собираете достаточно информации, чтобы включить расследование, прежде чем сообщить о проблеме корпорации Майкрософт. "Сам по себе стек вызовов недостаточен." Нам нужна более подробная информация, например:

    • Полное решение VS, включающее шаги по воспроизведению проблемы, включая описание среды (ОС, .NET и графики).
    • Трассировка отладки по времени и пути к проблеме.
    • Полный файл дампа аварийного сбоя.