Evitando vazamentos de memória
Ao usar controles Win2D em aplicativos XAML gerenciados, deve-se tomar cuidado para evitar ciclos de contagem de referência que possam impedir que esses controles sejam recuperados pelo coletor de lixo.
Você tem um problema se...
- Você está usando Win2D de uma linguagem .NET, como C# (não C++ nativo)
- Você usa um dos controles XAML do Win2D:
- Você se inscreve em eventos do controle Win2D (por exemplo,
Draw
,CreateResources
,SizeChanged
...) - Seu aplicativo se move para frente e para trás entre mais de uma página XAML
Se todas essas condições forem atendidas, um ciclo de contagem de referência impedirá que o controle Win2D seja coletado como lixo. Novos recursos Win2D são alocados cada vez que o aplicativo se move para uma página diferente, mas os antigos nunca são liberados para que a memória seja perdida. Para evitar isso, você deve adicionar código para interromper explicitamente o ciclo.
Como corrigir
Para quebrar o ciclo de contagem de referência e deixar sua página ser coletada como lixo:
- Conecte o evento
Unloaded
da página XAML que contém o controle Win2D - No manipulador
Unloaded
, chameRemoveFromVisualTree
no controle Win2D - No manipulador
Unloaded
, libere (definindo comonull
) quaisquer referências explícitas ao controle Win2D
Exemplo de código:
void page_Unloaded(object sender, RoutedEventArgs e)
{
this.canvas.RemoveFromVisualTree();
this.canvas = null;
}
Para obter exemplos de trabalho, consulte qualquer uma das páginas de demonstração da Galeria de exemplos.
Como testar perdas de ciclo
Para testar se seu aplicativo está interrompendo corretamente os ciclos de refcount, adicione um método finalizador a qualquer página XAML que contenha controles Win2D:
~MyPage()
{
System.Diagnostics.Debug.WriteLine("~" + GetType().Name);
}
Em seu construtor App
, configure um temporizador que garantirá que a coleta de lixo ocorra em intervalos regulares:
var gcTimer = new DispatcherTimer();
gcTimer.Tick += (sender, e) => { GC.Collect(); };
gcTimer.Interval = TimeSpan.FromSeconds(1);
gcTimer.Start();
Navegue até a página e, em seguida, afaste-se dela para alguma outra página. Se todos os ciclos tiverem sido interrompidos, você verá a saída Debug.WriteLine
no painel de saída do Visual Studio dentro de um ou dois segundos.
Chamar GC.Collect
é algo que interrompe e prejudica o desempenho, por isso remova esse código de teste assim que terminar os testes para vazamentos!
Os detalhes sangrentos
Um ciclo ocorre quando um objeto A tem uma referência a B, ao mesmo tempo que B também tem uma referência a A. Ou quando A faz referência a B, e B faz referência a C, enquanto C faz referência a A, etc.
Ao assinar eventos de um controle XAML, esse tipo de ciclo é praticamente inevitável:
- A página XAML contém referências a todos os controles contidos nela
- Os controles mantêm referências aos representantes do manipulador que foram inscritos em seus eventos
- Cada representante mantém uma referência à sua instância de destino
- Os manipuladores de eventos geralmente são métodos de instância da classe de página XAML, portanto, suas referências de instância de destino apontam de volta para a página XAML, criando um ciclo
Se todos os objetos envolvidos forem implementados no .NET, esses ciclos não serão um problema porque o .NET é lixo coletado e o algoritmo de coleta de lixo é capaz de identificar e recuperar grupos de objetos mesmo que eles estejam vinculados em um ciclo.
Ao contrário do .NET, o C++ gerencia a memória por contagem de referência, que não consegue detectar e recuperar ciclos de objetos. Apesar dessa limitação, os aplicativos C++ que usam Win2D não têm problemas porque os manipuladores de eventos C++ têm como padrão manter referências fracas em vez de fortes à instância de destino. Portanto, a página faz referência ao controle e o controle faz referência ao representante do manipulador de eventos, mas esse delegado não faz referência à página, portanto, não há ciclo.
O problema ocorre quando um componente WinRT C++, como Win2D, é usado por um aplicativo .NET:
- A página XAML faz parte do aplicativo, portanto, usa a coleta de lixo
- O controle Win2D é implementado em C++, portanto, usa contagem de referência
- O representante do manipulador de eventos faz parte do aplicativo, portanto, usa a coleta de lixo e mantém uma forte referência à instância de destino
Um ciclo está presente, mas os objetos Win2D que fazem parte desse ciclo não estão usando a coleta de lixo do .NET. Isso significa que o coletor de lixo não consegue ver toda a cadeia, portanto, não pode detectar ou recuperar os objetos. Quando isso ocorre, o aplicativo deve ajudar quebrando explicitamente o ciclo. Isso pode ser feito liberando todas as referências da página para o controle (conforme recomendado acima) ou liberando todas as referências do controle para representantes do manipulador de eventos que podem apontar de volta para a página (usando o evento Unloaded da página para cancelar a inscrição de todos os manipuladores de eventos).
Windows developer