Para ver el artículo en inglés, active la casilla Inglés. También puede ver el texto en inglés en una ventana emergente si pasa el puntero del mouse por el texto.
Traducción
Inglés

Optimizar el rendimiento: Comportamiento de objetos

 

Publicada: junio de 2016

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

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

WPF introduce un modelo de agentes de escucha de evento débil para los eventos que puede resultar de utilidad en aquellos casos en que resulta difícil seguir la traza de las relaciones de duración del objeto entre el origen y el agente de escucha. Algunos eventos de WPF existentes utilizan este modelo. Si implementa objetos con eventos personalizados, este modelo puede resultarle de utilidad. Para obtener información detallada, vea Modelos de evento débil.

Hay varias herramientas, tales como el generador de perfiles de CLR y el visor de espacio de trabajo, que pueden proporcionar información sobre el uso de memoria por parte de un proceso especificado. El generador de perfiles de CLR incluye varias vistas muy útiles del perfil de asignación, entre las que se incluye un histograma de tipos asignados, gráficos de asignación y llamadas, una escala temporal que muestra recolecciones de elementos no utilizados de varias generaciones y el estado resultante del montón administrado después de esas recolecciones, así como un árbol de llamadas que muestra las asignaciones por método y las cargas de ensamblado. Para obtener más información, visite .NET Framework Developer Center.

En general, tener acceso a una propiedad de dependencia de un objeto DependencyObject no es más lento que tener acceso a una propiedad de CLR. Aunque al establecer un valor de propiedad se produce una pequeña sobrecarga de rendimiento, la obtención de un valor es tan rápida como en una propiedad de CLR. Esta pequeña sobrecarga de rendimiento se compensa con el hecho de que las propiedades de dependencia admiten características robustas, tales como enlace de datos, animación, herencia y estilos. Para obtener más información, consulte Información general sobre las propiedades de dependencia.

Deben extremarse las precauciones al definir propiedades de dependencia en la aplicación. Si DependencyProperty sólo afecta a las opciones de metadatos de tipo de representación, y no a otras opciones de metadatos como AffectsMeasure, debe marcarla como a tal invalidando sus metadatos. Para obtener más información sobre cómo invalidar u obtener metadatos de propiedades, vea Metadatos de las propiedades de dependencia.

Puede ser más eficaz hacer que un controlador de cambio de propiedad invalide manualmente los pases de medida, organización y representación si no todos los cambios afectan realmente a la medición, organización y representación. Por ejemplo, podría optar por representar de nuevo un fondo únicamente cuando un valor sea superior a un límite establecido. En este caso, el controlador de cambio de propiedad únicamente invalidaría la representación cuando el valor superase ese límite establecido.

De manera predeterminada, las propiedades de dependencia registradas no se pueden heredar. Sin embargo, puede hacer explícitamente que cualquier propiedad sea heredable. Aunque se trata de una característica útil, convertir una propiedad en heredable afecta negativamente al rendimiento, porque aumenta el tiempo que se tarda en invalidar las propiedades.

Aunque llamar a RegisterClassHandler permite guardar el estado de instancia, es importante ser consciente de que se llama al controlador en cada instancia, lo que puede dar lugar a problemas de rendimiento. Únicamente debe utilizar RegisterClassHandler cuando la aplicación necesite que se guarde el estado de instancia.

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

Al crear una DependencyProperty, si lo desea puede establecer 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 máximo rendimiento, establezca PropertyMetadata durante la llamada a Register.

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

Cada objeto Freezable tiene un evento Changed que se provoca cada vez que cambia. Sin embargo, las notificaciones de cambio son costosas por lo que se refiere al rendimiento de la aplicación.

Estudie el ejemplo siguiente, en el que cada Rectangle utiliza el mismo objeto Brush:

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

De manera predeterminada, WPF proporciona un controlador para el evento Changed del objeto SolidColorBrush a fin de invalidar la propiedad Fill del objeto Rectangle. En este caso, cada vez que SolidColorBrush tiene que iniciar su evento Changed, es preciso 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 conlleva importante reducción del rendimiento. Además, agregar y quitar controladores en este punto también supone una intensa carga para el rendimiento, puesto que para hacerlo la aplicación tiene que recorrer la lista completa. Si en el escenario de aplicación SolidColorBrush nunca cambia, estará pagando el costo de mantener innecesariamente los controladores de eventos Changed.

Inmovilizar un objeto Freezable puede mejorar su rendimiento, porque ya no es preciso dedicar recursos a mantener notificaciones de cambio. En la tabla siguiente se muestra el tamaño de un SolidColorBrush simple cuando su propiedad IsFrozen está establecida en true, en comparación con cuando no lo está. Se basa en el supuesto de que se aplica un pincel a la propiedad Fill de diez objetos Rectangle.

Estado

Size

SolidColorBrush inmovilizado

212 bytes

SolidColorBrush no inmovilizado

972 bytes

En el ejemplo de código siguiente 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;
}

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

WPF también enlaza internamente los eventos Changed. Por ejemplo, todas las propiedades de dependencia que aceptan Freezable como valor escucharán automáticamente para detectar los eventos Changed. La propiedad Fill, que acepta un Brush, muestra este concepto.

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

Al asignar myBrush a myRectangle.Fill, un delegado que señala hacia atrás al objeto Rectangle se agregará 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 utilizados:

myRectangle = null;

En este caso, myBrush mantiene myRectangle activo y le devolverá la llamada cuando provoque su evento Changed. Observe que al asignar myBrush a la propiedad Fill de un nuevo Rectangle, simplemente se agrega otro controlador de eventos a myBrush.

La manera recomendada de limpiar estos tipos de objetos es quitar Brush de la propiedad Fill, lo que, a su vez, quitará el controlador de eventos de Changed.

myRectangle.Fill = null;
myRectangle = null;

WPF también proporciona una variación del elemento StackPanel que "virtualiza" automáticamente el contenido secundario enlazado a datos. En este contexto, el término "virtualizar" se refiere a una técnica por la que se genera un subconjunto de objetos a partir de un número mayor de elementos de datos en función de los elementos que están visibles en pantalla. Generar un gran número de elementos de interfaz de usuario cuando sólo pueden estar en pantalla algunos de ellos en un momento dado, requiere un uso intensivo, tanto de la memoria como del procesador. VirtualizingStackPanel (a través de la funcionalidad proporcionada por VirtualizingPanel) calcula los elementos visibles y trabaja con ItemContainerGenerator desde un control ItemsControl (como ListBox o ListView) para crear elementos únicamente para los elementos visibles.

Para optimizar el rendimiento, los objetos visuales correspondientes a estos elementos se generan o mantienen activos únicamente si están visibles en la pantalla. Cuando ya no se encuentran en el área visible del control, los objetos visuales se pueden quitar. Esto no debe confundirse con la virtualización de datos, donde los objetos de datos no están presentes en absoluto en la colección local, sino que se transmiten a medida que se necesitan.

En la tabla siguiente se muestra el tiempo transcurrido para agregar y representar 5000 elementos TextBlock en un StackPanel y en un VirtualizingStackPanel. En este escenario, las mediciones representan el tiempo transcurrido entre el momento de asociar una cadena de texto a la propiedad ItemsSource de un objeto ItemsControl hasta el momento en que los elementos de panel muestran la cadena de texto.

Panel host

Tiempo de representación (ms)

StackPanel

3210

VirtualizingStackPanel

46

Mostrar: