Otimizando o desempenho: comportamento do objeto
Entender o comportamento intrínseco de objetos WPF ajudará você a fazer as compensações certas entre a funcionalidade e o desempenho.
Não remover manipuladores de eventos em objetos pode manter objetos vivos
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 vivos por mais tempo do que o esperado. Ao executar a limpeza de um objeto que se registrou para escutar eventos de outro objeto, é essencial remover essa delegação 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 apresenta 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 a origem e o ouvinte são difíceis de acompanhar. Alguns eventos existentes do WPF usam esse padrão. Se você estiver implementando objetos com eventos personalizados, esse padrão poderá ser útil para você. Para obter detalhes, consulte Padrões de Eventos Fracos.
Há várias ferramentas, como o CLR Profiler e o Visualizador do Conjunto de Trabalho, que podem fornecer informações sobre o uso de memória de um processo especificado. O CLR Profiler inclui várias exibições muito úteis do perfil de alocação, incluindo um histograma de tipos alocados, grafos de alocação e chamada, uma linha de tempo mostrando coletas de lixo de várias gerações e o estado resultante do heap gerenciado após essas coleções e uma árvore de chamadas mostrando alocações por método e cargas de assembly. Para obter mais informações, consulte Performance.
Propriedades e objetos de dependência
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. Compensar a pequena sobrecarga de desempenho é o fato de que as propriedades de dependência dão suporte a recursos robustos, como associaçã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 de DependencyProperty
Você deve definir as propriedades de dependência em seu aplicativo com muito cuidado. Se o DependencyProperty afetar apenas as opções de metadados de tipo de renderização e não 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 metadados de propriedade de dependência.
Pode ser mais eficiente ter um manipulador de alteração de propriedade para invalidar manualmente as etapas de medição, organização e renderização, caso nem todas as alterações de propriedade afetem realmente a medição, organização e renderização. Por exemplo, você pode decidir renderizar novamente um plano de fundo somente quando um valor for maior 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 um DependencyProperty Herdável não é gratuito
Por padrão, as propriedades de dependência registradas são não herdáveis. No entanto, você pode tornar explicitamente qualquer propriedade herdável. Embora esse seja um recurso útil, converter uma propriedade para ser herdável afeta o desempenho aumentando o período de tempo para invalidação da propriedade.
Utilize o RegisterClassHandler com cuidado
Ao chamar RegisterClassHandler permite 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. Use RegisterClassHandler 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 DependencyProperty que requer um valor padrão, defina o valor usando os metadados padrão passados como um parâmetro para o método Register do DependencyProperty. Use essa técnica em vez de definir o valor da propriedade em um 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 seu objeto possa ter um construtor estático para chamar OverrideMetadata, essa não é a solução ideal e afetará o desempenho. Para obter melhor desempenho, configure o PropertyMetadata para Registerdurante a chamada.
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 aplicativo e reduz seu conjunto de trabalho. Para obter mais informações, consulte Visão Geral dos Objetos Congeáveis.
Cada Freezable tem um evento Changed que é gerado sempre que Freezable é alterado. No entanto, as notificações de alteração são caras em termos de desempenho do aplicativo.
Considere o exemplo a seguir no qual 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 Changed do objeto SolidColorBrush a fim de invalidar a propriedade Fill do objeto Rectangle. Nesse caso, sempre que o SolidColorBrush precisar disparar seu evento Changed, será necessário invocar a função callback para cada Rectangle— o acúmulo dessas invocações de função callback resulta em uma penalidade de desempenho significativa. Além disso, é muito exigente em termos de desempenho adicionar e remover manipuladores neste momento, pois o aplicativo teria que percorrer toda a lista para fazer isso. Se o cenário do seu aplicativo nunca alterar o SolidColorBrush, você estará pagando desnecessariamente o custo de manutenção de manipuladores de eventos de Changed.
Congelar um Freezable pode melhorar seu desempenho, pois ele não precisa mais gastar recursos na manutenção de notificações de alteração. A tabela abaixo mostra o tamanho de uma SolidColorBrush simples quando sua propriedade IsFrozen é definida como true
, em comparação a quando não é. Isso pressupõe a aplicação de um pincel na propriedade Fill de dez objetos Rectangle.
Estado | 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 modificados em objetos congeláveis não congelados podem manter objetos vivos
O delegado que um objeto passa para o evento Changed do objeto Freezable é efetivamente uma referência a esse objeto. Portanto, Changed manipuladores de eventos podem manter os objetos vivos por mais tempo do que o esperado. Ao executar a limpeza de um objeto registrado para ouvir o evento Changed de um objeto Freezable, é 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 levam Freezable como valor ouvirão automaticamente eventos Changed. A propriedade Fill, que aceita 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
para myRectangle.Fill
, um delegado que referencia o objeto Rectangle será adicionado ao evento Changed do objeto SolidColorBrush. Isso significa que o código a seguir não torna myRect
elegível para coleta de lixo:
myRectangle = null;
myRectangle = Nothing
Neste caso, myBrush
ainda mantém 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 ao 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 associado a dados. Nesse 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 em quais itens sã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 podem estar na tela em um determinado momento. VirtualizingStackPanel (por meio da funcionalidade fornecida pelo VirtualizingPanel) calcula itens visíveis e funciona com o ItemContainerGenerator de um ItemsControl (como ListBox ou ListView) para criar apenas elementos para itens visíveis.
Como otimização de desempenho, os objetos visuais desses itens só são gerados ou mantidos vivos se estiverem visíveis na tela. Quando eles não estiverem mais na área visível do controle, os objetos visuais poderão ser removidos. Isso não deve ser confundido com a virtualização de dados, em que os objetos de dados não estão todos presentes na coleção local, e sim transmitidos conforme necessário.
A tabela abaixo mostra o tempo decorrido ao adicionar e renderizar 5.000 elementos TextBlock em um StackPanel e em 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 host | tempo de renderização (ms) |
---|---|
StackPanel | 3210 |
VirtualizingStackPanel | 46 |
Consulte também
- otimizando de desempenho do aplicativo WPF
- planejamento de para de desempenho do aplicativo
- Maximizando o Hardware
- layout e design
- Gráficos 2D e Imagem
- Recursos de Aplicativo
- Texto
- Vinculação de Dados
- outras recomendações de desempenho
.NET Desktop feedback