Оптимизация производительности: поведение объекта

Понимание внутреннего поведения объектов WPF поможет найти оптимальное сочетание функциональных возможностей и производительности.

Не удаление обработчиков событий для объектов может поддерживать объекты в активном состоянии

Делегат, который объект передает в свое событие, фактически является ссылкой на этот объект. Таким образом, обработчики событий могут поддерживать объекты в активном состоянии дольше, чем планировалось. При выполнении очистки объекта, зарегистрированного для прослушивания события объекта, необходимо удалить этот делегат перед освобождением объекта. Сохранение ненужных объектов в активном состоянии увеличивает потребление памяти. Это особенно важно в тех случаях, когда объект является корневым элементом логического дерева или визуального дерева.

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

Существует несколько инструментов, таких как профилировщик CLR и Working Set Viewer, которые могут предоставлять сведения об использовании памяти указанным процессом. Профилировщик CLR включает ряд очень полезных представлений профиля выделения, включая гистограмму выделенных типов, диаграммы выделения и вызова, временную шкалу, показывающую сборку мусора разных поколений и итоговое состояние управляемой кучи после этих сборок, а также дерево вызовов, показывающее распределения по методам и загрузки сборок. Дополнительные сведения см. в разделе Производительность.

Свойства и объекты зависимостей

Как правило, доступ к свойству зависимостей объекта DependencyObject не медленнее, чем доступ к свойству CLR. Хотя существуют небольшие издержки производительности при задании значения свойства, получение значения происходит так же быстро, как получение значения из свойства CLR. Издержки производительности компенсируются за счет того, что свойства зависимостей поддерживают надежные функции, такие как привязка данных, анимация, наследование и задание стилей. Дополнительные сведения см. в обзоре свойств зависимостей.

Оптимизация DependencyProperty

Свойства зависимостей в приложении следует определять очень внимательно. Если ваше свойство DependencyProperty затрагивает только параметры метаданных с типом визуализации, но не другие параметры метаданных, такие как AffectsMeasure, следует пометить его как таковое путем переопределения его метаданных. Дополнительные сведения о переопределении или получении метаданных свойства см. в разделе Метаданные свойств зависимостей.

Возможно, более рационально использовать обработчик изменений свойств, чтобы аннулировать передачу размера, упорядочения и отображения вручную, если не все изменения свойств фактически влияют на размер, упорядочение и отображение. Например, вы можете решить заново отображать фон, только когда значение выше установленного ограничения. В этом случае обработчик изменений свойств будет аннулировать отображение, только когда значение превышает установленное ограничение.

Наследуемость DependencyProperty не обходится даром

По умолчанию зарегистрированные свойства зависимостей не являются наследуемыми. Однако вы можете явно сделать любое свойство наследуемым. Хотя это полезная возможность, преобразование свойства в наследуемое негативно влияет на производительность, увеличивая интервал времени для аннулирования свойства.

Используйте RegisterClassHandler с осторожностью

Хотя вызов RegisterClassHandler позволяет сохранить состояние экземпляра, важно знать, что обработчик вызывается в каждом экземпляре, что может вызвать проблемы с производительностью. Используйте только RegisterClassHandler, когда приложению требуется сохранять состояние экземпляра.

Установка значения по умолчанию для DependencyProperty во время регистрации

При создании DependencyProperty, которому требуется значение по умолчанию, задайте это значение с помощью метаданных по умолчанию, передаваемых в качестве параметра в метод Register класса DependencyProperty. Рекомендуется использовать именно этот метод вместо настройки значения свойства в конструкторе или в каждом экземпляре элемента.

Установка значения PropertyMetadata с помощью реестра

При создании DependencyProperty вы можете установить PropertyMetadata с помощью методов Register или OverrideMetadata. Хотя объект может иметь статический конструктор для вызова OverrideMetadata, это не является оптимальным решением и повлияет на производительность. Для обеспечения лучшей производительности задайте PropertyMetadata во время вызова Register.

Объекты Freezable

Freezable — это особый тип объекта, имеющий два состояния: фиксированное и нефиксированное. Фиксация объектов везде, где это возможно, улучшает производительность приложения и уменьшает его рабочий набор. Дополнительные сведения см. в разделе Общие сведения об объектах класса Freezable.

Каждый объект Freezable имеет событие Changed, возникающее при каждом его изменении. Однако уведомления об изменениях обходятся дорого с точки зрения производительности приложения.

Рассмотрим следующий пример, в котором каждый Rectangle использует один и тот же объект Brush.

rectangle_1.Fill = myBrush;
rectangle_2.Fill = myBrush;
rectangle_3.Fill = myBrush;
// ...
rectangle_10.Fill = myBrush;
rectangle_1.Fill = myBrush
rectangle_2.Fill = myBrush
rectangle_3.Fill = myBrush
' ...
rectangle_10.Fill = myBrush

По умолчанию WPF предоставляет обработчик событий для события Changed объекта SolidColorBrush, чтобы сделать недействительным свойство Fill объекта Rectangle. В этом случае каждый раз, когда объект SolidColorBrush вызывает свое событие Changed, необходимо инициировать функцию обратного вызова для каждого Rectangle — накопление инициаций этих функций обратного вызова приводит к значительному снижению производительности. Кроме того, довольно затратно с точки зрения производительности добавлять и удалять обработчики на этом этапе, так как приложению потребуется пройти по всему списку, чтобы сделать это. Если сценарий приложения никогда не изменяет объект SolidColorBrush, вы без всякой необходимости будете нести затраты на поддержку обработчиков событий Changed.

Фиксация объекта Freezable может повысить его производительность, поскольку больше не требуется тратить ресурсы на уведомления об изменениях. В следующей таблице показан размер простого объекта SolidColorBrush со свойством IsFrozen, для которого установлено значение true, по сравнению с тем же объектом без этого свойства. Это подразумевает применение одной кисти к свойству Fill десяти объектов Rectangle.

Штат Размер
Зафиксированный SolidColorBrush 212 байт
Не зафиксированный SolidColorBrush 972 байта

Следующий пример кода демонстрирует эту концепцию.

Brush frozenBrush = new SolidColorBrush(Colors.Blue);
frozenBrush.Freeze();
Brush nonFrozenBrush = new SolidColorBrush(Colors.Blue);

for (int i = 0; i < 10; i++)
{
    // Create a Rectangle using a non-frozed Brush.
    Rectangle rectangleNonFrozen = new Rectangle();
    rectangleNonFrozen.Fill = nonFrozenBrush;

    // Create a Rectangle using a frozed Brush.
    Rectangle rectangleFrozen = new Rectangle();
    rectangleFrozen.Fill = frozenBrush;
}
Dim frozenBrush As Brush = New SolidColorBrush(Colors.Blue)
frozenBrush.Freeze()
Dim nonFrozenBrush As Brush = New SolidColorBrush(Colors.Blue)

For i As Integer = 0 To 9
    ' Create a Rectangle using a non-frozed Brush.
    Dim rectangleNonFrozen As New Rectangle()
    rectangleNonFrozen.Fill = nonFrozenBrush

    ' Create a Rectangle using a frozed Brush.
    Dim rectangleFrozen As New Rectangle()
    rectangleFrozen.Fill = frozenBrush
Next i

Обработчики событий изменений Changed в нефиксированных объектах Freezable могут поддерживать объекты в активном состоянии

Делегат, который объект передает в событие Changed объекта Freezable, фактически является ссылкой на этот объект. Таким образом, обработчики событий Changed могут поддерживать объекты в активном состоянии дольше, чем планировалось. При выполнении очистки объекта, зарегистрированного для прослушивания события Changed объекта Freezable, необходимо удалить этот делегат перед освобождением объекта.

WPF также подключает событие Changed внутренним образом. Например, все свойства зависимостей, принимающие Freezable в качестве значения, будут прослушивать события Changed автоматически. Свойство Fill, которое принимает Brush, иллюстрирует эту концепцию.

Brush myBrush = new SolidColorBrush(Colors.Red);
Rectangle myRectangle = new Rectangle();
myRectangle.Fill = myBrush;
Dim myBrush As Brush = New SolidColorBrush(Colors.Red)
Dim myRectangle As New Rectangle()
myRectangle.Fill = myBrush

При назначении myBrush свойству myRectangle.Fill делегат, указывающий обратно на объект Rectangle, будет добавлен в событие Changed объекта SolidColorBrush. Это означает, что следующий код в действительности не разрешает myRect сборку мусора.

myRectangle = null;
myRectangle = Nothing

В этом случае myBrush по-прежнему сохраняет myRectangle в активном состоянии и снова вызовет его при срабатывании его события Changed. Обратите внимание, что при назначении myBrush свойству Fill нового объекта Rectangle будет просто добавляться другой обработчик событий в myBrush.

Рекомендуемый способ очистки этих типов объектов — удаление Brush из свойства Fill, что, в свою очередь, приведет к удалению обработчика событий Changed.

myRectangle.Fill = null;
myRectangle = null;
myRectangle.Fill = Nothing
myRectangle = Nothing

Виртуализация пользовательского интерфейса

WPF также предлагает вариант элемента StackPanel, который автоматически «виртуализирует» содержимое дочерних элементов с привязкой к данным. В данном контексте слово «виртуализация» означает способ, с помощью которого подмножество объектов создается из большего количества элементов данных в зависимости от того, какие из элементов отображаются на экране. Как для памяти, так и для процессора затратно создавать большое число элементов пользовательского интерфейса, при том, что только несколько из них могут отображаться на экране одновременно. VirtualizingStackPanel (с помощью функций, предоставляемых VirtualizingPanel) вычисляет видимые объекты и при помощи элемента ItemContainerGenerator из ItemsControl (например, ListBox или ListView) создает только элементы для видимых объектов.

Для оптимизации производительности визуальные объекты для этих элементов создаются или поддерживаются в активном состоянии, только если они будут отображаться на экране. Если они больше не находятся в видимой области элемента управления, визуальные объекты могут быть удалены. Это не следует путать с виртуализацией данных, где не все объекты данных присутствуют в локальной коллекции, а скорее, передаются в потоке при необходимости.

В следующей таблице показано затраченное время на добавление и отрисовку 5000 элементов TextBlock в StackPanel и VirtualizingStackPanel. В этом случае измеряется время между присоединением текстовой строки к свойству ItemsSource объекта ItemsControl и отображением этой текстовой строки элементами панели.

Главная панель Время отрисовки (мс)
StackPanel 3210
VirtualizingStackPanel 46

См. также