Compartilhar via


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...

Se todas essas condições forem atendidas, um ciclo de contagem de referência impedirá que o controle Win2D seja coletado pelo coletor de lixo. Os novos recursos do Win2D são alocados sempre que o aplicativo é movido para uma página diferente, mas os antigos nunca são liberados para que a memória seja vazada. Para evitar isso, você deve adicionar código para interromper explicitamente o ciclo.

Como corrigir

Para quebrar o ciclo de contagem de referências e permitir que sua página seja coletada como lixo:

  • Conectar o evento Unloaded da página XAML que contém o controle Win2D
  • No manipulador de Unloaded, chame RemoveFromVisualTree no controle Win2D
  • Unloaded No manipulador, libere (definindo como null) quaisquer referências explícitas ao controle Win2D

Código de exemplo:

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 vazamentos de ciclo

Para testar se o aplicativo está interrompendo corretamente os ciclos de recontagem, adicione um método de finalizador a quaisquer páginas XAML que contenham controles Win2D:

~MyPage()
{
    System.Diagnostics.Debug.WriteLine("~" + GetType().Name);
}

Em seu App construtor, 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, vá para 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 em um segundo ou dois.

Observe que a chamada GC.Collect é disruptiva e prejudica o desempenho, portanto, você deve remover esse código de teste assim que concluir o teste 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 delegado possui uma referência à sua instância alvo
  • Os manipuladores de eventos normalmente 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 é gerenciado por coleta de lixo, e o algoritmo de coleta de lixo consegue identificar e recuperar grupos de objetos, mesmo que estejam vinculados em um ciclo.

Ao contrário do .NET, o C++ gerencia a memória por meio da contagem de referência, que não consegue detectar e recuperar ciclos de objetos. Apesar dessa limitação, os aplicativos C++ que usam o Win2D não têm problema porque os manipuladores de eventos C++ normalmente mantêm referências fracas em vez de fortes para sua instância de destino. Portanto, a página faz referência ao controle, e o controle faz referência ao delegado do manipulador de eventos, mas esse delegado não faz referência de volta à página, eliminando assim qualquer ciclo.

O problema ocorre quando um componente do WinRT do C++, como o Win2D, é usado por um aplicativo .NET:

  • A página XAML faz parte do aplicativo, portanto, usa a coleta de lixo
  • O controle Win2D é implementado no C++, portanto, usa a contagem de referência
  • O delegado do manipulador de eventos faz parte do aplicativo, portanto, usa a coleta de lixo e mantém uma referência forte à instância de destino

Um ciclo está presente, mas os objetos Win2D que participam 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, ele não pode detectar ou recuperar os objetos. Quando isso ocorre, o aplicativo deve ajudar interrompendo 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 delegados do manipulador de eventos que podem apontar de volta para a página (usando o evento Unloaded da página para cancelar a assinatura de todos os manipuladores de eventos).