Optimizar el rendimiento: Comportamiento de objetos

Entender el comportamiento intrínseco de los objetos WPF le ayudará a lograr un equilibrio correcto entre funcionalidad y rendimiento.

No quitar los controladores de eventos en objetos puede mantener los objetos activos

El delegado que un objeto pasa a su evento es realmente una referencia a ese objeto. Por lo tanto, los controladores de eventos pueden mantener los objetos activos durante más tiempo de lo esperado. Al realizar la limpieza de un objeto registrado para escuchar los eventos de un objeto, es esencial quitar ese delegado antes de liberar el objeto. Mantener activos objetos innecesarios aumenta el uso de memoria de la aplicación. Esto es especialmente cierto cuando el objeto es la raíz de un árbol lógico o un árbol visual.

WPF presenta un patrón de agente de escucha de evento débil para los eventos que pueden ser útiles en situaciones donde es difícil mantener el seguimiento de las relaciones de vigencia de objeto entre el origen y el agente de escucha. Algunos eventos WPF existentes usan este patrón. Si implementa objetos con eventos personalizados, este patrón puede resultarle de utilidad. Para obtener más información, consulte Modelos de evento débil.

Hay varias herramientas, como CLR Profiler y Visor de espacios de trabajo, que pueden proporcionar información sobre el uso de memoria de un proceso especificado. CLR Profiler incluye varias vistas muy útiles del perfil de asignación, incluido un histograma de tipos asignados, gráficos de asignación y llamadas, una línea de tiempo que muestra recolecciones de elementos no usados de varias generaciones y el estado resultante del montón administrado después de esas colecciones, y un árbol de llamadas que muestra las asignaciones por método y las cargas de ensamblado. Para más información, vea Rendimiento.

Propiedades y objetos de dependencia

En general, obtener acceso a una propiedad de dependencia de un objeto DependencyObject no es más lento que obtener acceso a una propiedad CLR. Aunque hay una pequeña sobrecarga de rendimiento para establecer un valor de propiedad, obtener un valor es tan rápido como obtener el valor de una propiedad CLR. El desplazamiento de la pequeña sobrecarga de rendimiento es el hecho de que las propiedades de dependencia admiten características robustas, como el enlace de datos, la animación, la herencia y los estilos. Para obtener más información sobre las propiedades de dependencia, vea Información general sobre las propiedades de dependencia.

Optimizaciones de DependencyProperty

Debe definir las propiedades de dependencia en la aplicación con mucho cuidado. Si su DependencyProperty afecta solo a las opciones de metadatos de tipo de representación, en lugar de a otras opciones de metadatos como AffectsMeasure, debe marcarlas como tal reemplazando sus metadatos. Para obtener más información sobre cómo invalidar u obtener los metadatos de las propiedades, consulte Metadatos de las propiedades de dependencia.

Puede ser más eficaz hacer que un controlador de cambio de propiedad invalide los pases de medición, organización y representación manualmente si todos los cambios de propiedad no afectan realmente a la medición, organización y representación. Por ejemplo, es posible que decida volver a representar un fondo solo cuando un valor sea mayor que un límite establecido. En este caso, el controlador de cambios de propiedades solo invalidará la representación cuando el valor supere el límite establecido.

Hacer que DependencyProperty sea heredable tiene consecuencias

De forma predeterminada, las propiedades de dependencia registradas no son heredables. Sin embargo, puede hacer explícitamente que una propiedad sea heredable. Aunque es una característica útil, convertir una propiedad a heredable afecta al rendimiento, ya que aumenta la cantidad de tiempo para la invalidación de propiedades.

Usar RegisterClassHandler con cuidado

Llamar a RegisterClassHandler le permite guardar el estado de la instancia, pero es importante tener en cuenta que se llama al controlador en cada instancia, lo que puede causar problemas de rendimiento. Use solo RegisterClassHandler cuando su aplicación requiera que guarde el estado de la instancia.

Establecer el valor predeterminado para DependencyProperty durante el registro

Al crear una DependencyProperty que requiera un valor predeterminado, establezca el valor usando los metadatos predeterminados que se pasaron como un parámetro al método Register de la DependencyProperty. Use esta técnica en lugar de establecer el valor de propiedad en un constructor o en cada instancia de un elemento.

Establecer el valor de PropertyMetadata mediante el Registro

Al crear una DependencyProperty, tiene la opción de establecer los PropertyMetadata mediante los métodos Register o OverrideMetadata. Aunque el objeto podría tener un constructor estático para llamar a OverrideMetadata, no es la solución óptima y afecta negativamente al rendimiento. Para obtener el mejor rendimiento, establezca el PropertyMetadata durante la llamada al Register.

Objetos Freezable

Un objeto Freezable es un tipo especial de objeto que tiene dos estados: no inmovilizado e inmovilizado. Inmovilizar objetos siempre que sea posible mejora el rendimiento de la aplicación y reduce su conjunto de trabajo. Para obtener más información, consulte Información general sobre objetos Freezable.

Cada Freezable tiene un evento Changed que se genera cada vez que cambia. Sin embargo, las notificaciones de cambio son costosas en términos de rendimiento de la aplicación.

Considere el siguiente ejemplo en el que cada Rectangle usa el mismo objeto 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

De forma predeterminada, WPF proporciona un controlador de eventos para el SolidColorBrush evento Changed del objeto para invalidar la propiedad Rectangle del objeto Fill. En este caso, cada vez que SolidColorBrush tiene que iniciar su evento Changed es necesario invocar la función de devolución de llamada para cada Rectangle. La acumulación de estas invocaciones de la función de devolución de llamada impone un penalización en el rendimiento significativa. Además, es muy intensivo para el rendimiento agregar y quitar controladores en este punto, ya que la aplicación deberá recorrer toda la lista para ello. Si su escenario de aplicación nunca cambia el SolidColorBrush, pagará el coste de mantener los controladores de eventos Changed de forma innecesaria.

Inmovilizar un objeto Freezable puede mejorar su rendimiento, ya que no necesita dedicar recursos a mantener notificaciones de cambios. La siguiente tabla muestra el tamaño de un SolidColorBrush simple cuando su propiedad IsFrozen se establece en true, con respecto a cuando no. Esto asume que se aplica un pincel a la propiedad Fill de diez objetos Rectangle.

State Tamaño
SolidColorBrush congelado 212 bytes
SolidColorBrush no congelado 972 bytes

En el siguiente ejemplo de código se muestra este concepto:

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

Los controladores modificados de objetos Freezable no inmovilizados pueden mantener los objetos activos

El delegado que un objeto pasa a un evento Changed de un objeto Freezable es realmente una referencia a ese objeto. Por lo tanto, los controladores de eventos de Changed pueden mantener los objetos activos durante más tiempo de lo esperado. Al realizar la limpieza de un objeto registrado para escuchar los eventos Changed de un objeto Freezable, es esencial quitar ese delegado antes de liberar el objeto.

WPF también enlaza eventos Changed internamente. Por ejemplo, todas las propiedades de dependencia que toman Freezable como un valor escucharán a los eventos Changed automáticamente. La propiedad Fill, que toma un Brush, ilustra este concepto.

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

En la asignación de myBrush a myRectangle.Fill, un delegado que señala de nuevo al objeto Rectangle se añadirá al evento Changed del objeto SolidColorBrush. Esto significa que el código siguiente realmente no hace que myRect sea apto para la recolección de elementos no usados:

myRectangle = null;
myRectangle = Nothing

En este caso, myBrush mantiene activo a myRectangle y volverá a llamarlo cuando se active su evento Changed. Tenga en cuenta que asignar myBrush a la propiedad Fill de un nuevo Rectangle solo añadirá otro controlador de eventos al myBrush.

La forma recomendada de limpiar estos tipos de objetos es quitar el Brush de la propiedad Fill, lo que eliminará el controlador de eventos Changed.

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

Virtualización de la interfaz de usuario

WPF también proporciona una variación del elemento StackPanel que "virtualiza" automáticamente el contenido secundario enlazado a datos. En este contexto, la palabra "virtualizar" hace referencia a una técnica que permite generar un subconjunto de objetos a partir de un número de elementos de datos más grande basados en los elementos que están visibles en pantalla. Es intensiva, tanto en términos de memoria como de procesador, y permite generar un gran número de elementos de interfaz de usuario cuando solo pueden estar en pantalla algunos de ellos en un momento dado. VirtualizingStackPanel (mediante la funcionalidad que proporciona VirtualizingPanel) calcula los elementos visibles y funciona con ItemContainerGenerator desde ItemsControl (como ListBox o ListView) para crear elementos solo para los elementos visibles.

Como optimización del rendimiento, los objetos visuales para estos elementos solo se generan o se mantienen activos si están visibles en pantalla. Cuando ya no están en el área visible del control, se pueden quitar los objetos visuales. Esto no se debe confundir con la virtualización de datos, donde los objetos de datos no existen en la colección local, sino que se transmiten según sea necesario.

La siguiente tabla muestra el tiempo transcurrido al añadir y representar 5000 elementos TextBlock en un StackPanel y un VirtualizingStackPanel. En este escenario, las mediciones representan el tiempo que transcurre entre que se adjunta una cadena de texto a la propiedad ItemsSource de un objeto ItemsControl y que los elementos del panel muestran la cadena de texto.

Panel de host Tiempo de representación (ms)
StackPanel 3210
VirtualizingStackPanel 46

Vea también