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


Сбои потока обработки WPF

В этом документе рассматриваются сбои в потоке отрисовки WPF, при этом особое внимание уделяется тем, кто вызывает исключение SyncFlush или NotifyPartitionIsZombie что приводит к зависаниям WaitForNextMessage приложений.SynchronizeChannel

Исходная версия продукта: платформа .NET Framework 4.8

Сбои в SyncFlush, WaitForNextMessage, SyncChannel и NotifyPartitionIsZombie

Разработчики часто сталкиваются с проблемами, связанными с ошибками потоков в приложениях Windows Presentation Foundation (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

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

Описание потока отрисовки WPF и его отличия от потока пользовательского интерфейса

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

Причины сбоев, упомянутых выше

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

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

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

Распространенные причины сбоев

Наиболее распространенный сегмент ошибок потока отрисовки 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. Обновите последнюю версию и уровень пакета обновления .NET, доступный для целевой платформы.

  4. Обновление до последней операционной системы.

  5. Отключите использование Windows.AllowsTransparency и Popup.AllowsTransparency в приложении.

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

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

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