Partilhar via


Otimizando o desempenho: comportamento do objeto

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

Não remover gestores de eventos em objetos pode 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 os objetos ativos por mais tempo do que o esperado. Ao limpar um objeto que foi registado para escutar o evento de um objeto, é essencial remover essa delegação antes de liberar o objeto. Manter objetos desnecessários vivos aumenta o uso de memória do aplicativo. Isso é especialmente verdadeiro quando o objeto é a raiz de uma árvore lógica ou uma árvore visual.

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

Existem várias ferramentas, como o CLR Profiler e o Working set Viewer, que podem fornecer informações sobre o uso de memória de um processo especificado. O CLR Profiler inclui várias visualizações muito úteis do perfil de alocação, incluindo um histograma de tipos alocados, gráficos de alocação e chamada, uma linha do tempo mostrando coleções de lixo de várias gerações e o estado resultante do heap gerenciado após essas coletas e uma árvore de chamadas mostrando alocações por método e cargas de montagem. Para obter mais informações, consulte Desempenho.

Propriedades e objetos de dependência

Em geral, aceder a uma propriedade de dependência de um DependencyObject não é mais lento do que aceder a uma propriedade de 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. Compensando a pequena sobrecarga de desempenho está o fato de que as propriedades de dependência oferecem suporte a recursos robustos, como vinculação de dados, animação, herança e estilo. Para obter mais informações, consulte Visão Geral das Propriedades de Dependência.

Otimizações "DependencyProperty"

Você deve definir as propriedades de dependência em seu aplicativo com muito cuidado. Se o seu DependencyProperty afetar apenas as opções de metadados de tipo de renderização, em vez de outras opções de metadados, como AffectsMeasure, você deverá marcá-lo como tal substituindo seus metadados. Para obter mais informações sobre como substituir ou obter metadados de propriedade, consulte Dependency Property Metadata.

Pode ser mais eficiente fazer com que um manipulador de alteração de propriedade invalide manualmente as etapas de medição, arranjo e renderização se nem todas as alterações de propriedade afetarem realmente essas etapas. Por exemplo, você pode decidir renderizar novamente um plano de fundo somente quando um valor for maior do que um limite definido. Nesse caso, o manipulador de alteração de propriedade só invalidaria a renderização quando o valor excedesse o limite definido.

Tornar uma DependencyProperty hereditável não é de graça

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

Use RegisterClassHandler com cuidado

Embora a chamada RegisterClassHandler permita salvar 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. Use RegisterClassHandler somente quando seu aplicativo exigir que você salve o estado da instância.

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

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

Definir o valor PropertyMetadata usando Register

Ao criar um DependencyProperty, você tem a opção de definir o PropertyMetadata usando os métodos Register ou OverrideMetadata. Embora o 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

Um 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 tem um evento Changed que é acionado sempre que ocorre uma alteração. No entanto, as notificações de alteração são dispendiosas em termos de desempenho do aplicativo.

Considere o seguinte exemplo em que cada Rectangle usa o mesmo 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

Por padrão, o WPF fornece um manipulador de eventos para o evento SolidColorBrush do objeto Changed para invalidar a propriedade Rectangle do objeto Fill. Nesse caso, cada vez que o SolidColorBrush precisa disparar o seu evento Changed, é necessário invocar a função callback para cada Rectangle—o acúmulo destas invocações de funções callback impõe uma penalidade de desempenho significativa. Além disso, é muito intensivo em termos de desempenho adicionar e remover manipuladores neste momento, uma vez que o aplicativo teria que percorrer toda a lista para fazê-lo. Se o cenário do aplicativo nunca alterar o SolidColorBrush, você pagará o custo de manter Changed 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 SolidColorBrush simples quando sua propriedade IsFrozen está definida como true, em comparação com quando não está. Isso pressupõe a aplicação de um pincel ao atributo Fill de dez objetos Rectangle.

Estado Tamanho
Congelados SolidColorBrush 212 Bytes
SolidColorBrush não congelado 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 Freezables descongelados podem manter objetos vivos

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

O WPF também conecta eventos Changed internamente. Por exemplo, todas as propriedades de dependência que tomam Freezable como valor irão monitorizar automaticamente os eventos de Changed. A propriedade Fill, 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 myBrush a myRectangle.Fill, um delegado que referencia o objeto Rectangle será adicionado ao evento SolidColorBrush do objeto Changed. Isso significa que o código a seguir não torna o myRect elegível para coleta de lixo:

myRectangle = null;
myRectangle = Nothing

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

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

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 StackPanel que "virtualiza" automaticamente o conteúdo filho vinculado a dados. Neste contexto, a palavra virtualizar refere-se a uma técnica pela qual um subconjunto de objetos é gerado a partir de um número maior de itens de dados com base nos quais os itens são visíveis na tela. É intensivo, tanto em termos de memória quanto de processador, gerar um grande número de elementos da interface do usuário quando apenas alguns podem estar na tela em um determinado momento. VirtualizingStackPanel (através da funcionalidade fornecida pelo VirtualizingPanel) calcula itens visíveis e trabalha com o ItemContainerGenerator a partir de uma ItemsControl (como ListBox ou ListView) para criar apenas elementos para itens visíveis.

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

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

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

Ver também