Otimizando desempenho: comportamento do objeto

Compreender o comportamento intrínseco dos objetos WPF ajudará você a fazer as compensações corretas entre funcionalidade e desempenho.

Não remover manipuladores de eventos em objetos poderá manter os objetos ativos

O delegado que um objeto passa para seu evento é, efetivamente, uma referência a esse objeto. Portanto, os manipuladores de eventos podem manter objetos ativos por mais tempo que o esperado. Ao realizar a limpeza de um objeto que tenha sido registrado para escutar um evento de objeto, é essencial remover esse delegado antes de liberar o objeto. Manter objetos desnecessários ativos aumenta o uso de memória do aplicativo. Isso é especialmente verdadeiro quando o objeto é a raiz de uma árvore lógica ou de uma árvore visual.

O WPF introduz um padrão de ouvinte de eventos fraco para eventos que pode ser útil em situações em que as relações de tempo de vida do objeto entre a origem e o ouvinte são difíceis de acompanhar. Alguns eventos WPF existentes usam esse padrão. Se estiver implementando objetos com eventos personalizados, esse padrão poderá ser útil para você. Para obter detalhes, consulte Padrões de evento fracos.

Há várias ferramentas, como o Criador de Perfil CLR e o Visualizador de Conjunto de Trabalho, que podem fornecer informações sobre o uso de memória de um processo especificado. O Criador de Perfil CLR inclui várias exibições muito úteis do perfil de alocação, incluindo um histograma de tipos alocados, grafos de alocação e de chamadas, uma linha do tempo mostrando coletas de lixo de várias gerações e o estado resultante do heap gerenciado após essas coletas, além de uma árvore de chamadas mostrando as cargas de assembly e alocações por método. Para obter mais informações, consulte Desempenho.

Propriedades de dependência e objetos

Em geral, acessar uma propriedade de dependência de um DependencyObject não é mais lento do que acessar uma propriedade CLR. Embora haja uma pequena sobrecarga de desempenho para definir um valor de propriedade, obter um valor é tão rápido quanto obter o valor de uma propriedade CLR. O fato de as propriedades de dependência darem suporte a recursos robustos, como vinculação de dados, animação, herança e estilos é a compensação pela pequena sobrecarga de desempenho. Para obter mais informações, consulte Visão geral sobre propriedades de dependência.

Otimizações de DependencyProperty

Você deve definir as propriedades de dependência em seu aplicativo com muito cuidado. Se seus afetos renderizarem apenas opções de metadados de tipo, em vez de outras opções de metadados, como , você deverá marcá-lo como AffectsMeasuretal substituindo seus DependencyProperty metadados. Para obter mais informações sobre como substituir ou obter metadados de propriedades, consulte Metadados de propriedades de dependência.

Talvez seja mais eficiente fazer com que um manipulador de alterações de propriedade invalide as passagens de medida, organização e renderização manualmente se nem todas as alterações de propriedade afetarem, na verdade, a medida, a organização e a renderização. Por exemplo, você pode decidir renderizar novamente uma tela de fundo apenas quando um valor for maior que um limite definido. Nesse caso, seu manipulador de alteração de propriedade só invalidaria a renderização quando o valor excedesse o limite definido.

Há consequências em tornar uma DependencyProperty herdável

Por padrão, as propriedades de dependência registradas são não herdáveis. No entanto, você pode explicitamente tornar qualquer propriedade herdável. Embora esse seja um recurso útil, converter uma propriedade para se tornar herdável afeta o desempenho, aumentando o período de tempo para a invalidação da propriedade.

Usar o RegisterClassHandler com cuidado

Embora a chamada RegisterClassHandler permita que você salve o estado da instância, é importante estar ciente de que o manipulador é chamado em todas as instâncias, o que pode causar problemas de desempenho. RegisterClassHandler Use somente quando o aplicativo exigir que você salve o estado da instância.

Definir o valor padrão para uma DependencyProperty durante o registro

Ao criar um que requer um valor padrão, defina o valor usando os metadados padrão passados como um DependencyProperty parâmetro para o RegisterDependencyPropertymétodo do . Use esta técnica em vez de configurar o valor da propriedade em um construtor ou em cada instância de um elemento.

Definir o valor de PropertyMetadata usando o registro

Ao criar um DependencyProperty, você tem a opção de definir o PropertyMetadata uso dos Register métodos ou OverrideMetadata . Embora seu objeto possa ter um construtor estático para chamar OverrideMetadata, essa não é a solução ideal e afetará o desempenho. Para obter o melhor desempenho, defina o PropertyMetadata durante a chamada como Register.

Objetos congeláveis

A Freezable é um tipo especial de objeto que tem dois estados: descongelado e congelado. Congelar objetos sempre que possível melhora o desempenho do seu aplicativo e reduz seu conjunto de trabalho. Para obter mais informações, consulte a Visão geral de objetos congeláveis.

Cada Freezable um tem um Changed evento que é levantado sempre que muda. No entanto, as notificações de alteração são dispendiosas em termos de desempenho do aplicativo.

Considere o exemplo a seguir em que cada um Rectangle usa o mesmo Brush objeto:

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

Por padrão, o WPF fornece um manipulador de eventos para o SolidColorBrush evento do objeto a fim de invalidar a Rectangle propriedade do FillChanged objeto. Nesse caso, cada vez que o evento tem que dispararChanged, é necessário invocar a função de retorno de chamada para cada Rectangle— o SolidColorBrush acúmulo dessas chamadas de função de retorno de chamada impõe uma penalidade de desempenho significativa. Além disso, adicionar e remover manipuladores neste ponto exigiria muito do desempenho, já que o aplicativo teria que percorrer toda a lista para fazer isso. Se o cenário do aplicativo nunca alterar o , você pagará o SolidColorBrushcusto de manutenção Changed de manipuladores de eventos desnecessariamente.

Congelar um Freezable pode melhorar seu desempenho, porque ele não precisa mais gastar recursos na manutenção de notificações de alteração. A tabela abaixo mostra o tamanho de um simples SolidColorBrush quando sua IsFrozen propriedade é definida como true, em comparação com quando não está. Isso pressupõe a aplicação de um pincel à Fill propriedade de dez Rectangle objetos.

State Tamanho
Congelado SolidColorBrush 212 bytes
Não congelado SolidColorBrush 972 bytes

O exemplo de código a seguir demonstra esse conceito:

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

Manipuladores alterados em Congeláveis descongelados podem manter objetos ativos

O delegado que um objeto passa para o evento de Changed um Freezable objeto é efetivamente uma referência a esse objeto. Portanto, Changed os manipuladores de eventos podem manter os objetos vivos por mais tempo do que o esperado. Ao executar a limpeza de um objeto que se registrou para ouvir o evento de Changed um Freezable objeto, é essencial remover esse delegado antes de liberar o objeto.

O WPF também conecta Changed eventos internamente. Por exemplo, todas as propriedades de dependência que tomam Freezable como um valor escutarão Changed eventos automaticamente. A Fill propriedade, que leva um Brush, ilustra esse conceito.

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

Na atribuição de a myRectangle.Fill, um delegado apontando de myBrush volta para o Rectangle objeto será adicionado ao SolidColorBrush evento do Changed objeto. Isso significa que o código a seguir, na verdade, não torna o myRect qualificado para a coleta de lixo:

myRectangle = null;
myRectangle = Nothing

Neste caso myBrush ainda está se mantendo myRectangle vivo e chamará de volta quando disparar seu Changed evento. Observe que myBrush atribuir à Fill propriedade de um novo Rectangle simplesmente adicionará outro manipulador de eventos ao myBrush.

A maneira recomendada de limpar esses tipos de objetos é remover o da propriedade, que Fill por sua vez removerá o BrushChanged manipulador de eventos.

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

Virtualização da interface do usuário

O WPF também fornece uma variação do elemento que "virtualiza" automaticamente o StackPanel conteúdo filho vinculado a dados. Nesse contexto, a palavra virtualizar refere-se a uma técnica pela qual um subconjunto dos objetos são gerados de um grande número de itens de dados com base em quais itens estão visíveis na tela. É intensivo, tanto em termos de memória quanto de processador, gerar um grande número de elementos de interface do usuário quando apenas alguns deles podem estar na tela em determinado momento. VirtualizingStackPanel (através da funcionalidade fornecida por VirtualizingPanel) Calcula itens visíveis e trabalha com o ItemContainerGenerator de um ItemsControl (como ListBox ou ListView) para criar apenas elementos para itens visíveis.

Como uma otimização de desempenho, os objetos visuais para esses itens serão gerados ou mantidos ativos somente se estiverem visíveis na tela. Quando não estão mais na área visível do controle, os objetos visuais podem ser removidos. Isso não deve ser confundido com virtualização de dados, em que os objetos de dados não estão todos presentes na coleção local mas, em vez disso, são transmitidos conforme necessário.

A tabela abaixo mostra o tempo decorrido adicionando e renderizando 5000 TextBlock elementos a e StackPanel a .VirtualizingStackPanel Nesse cenário, as medidas representam o tempo entre anexar ItemsSource uma cadeia de caracteres de texto à propriedade de um ItemsControl objeto até o momento em que os elementos do painel exibem a cadeia de caracteres de texto.

Painel de host Tempo de renderização (ms)
StackPanel 3210
VirtualizingStackPanel 46

Confira também