Condividi tramite


Evitare perdite di memoria

Quando si usano controlli Win2D nelle applicazioni XAML gestite, è necessario prestare attenzione a evitare cicli di conteggio dei riferimenti che potrebbero impedire che questi controlli vengano recuperati dal Garbage Collector.

Hai un problema se...

Se vengono soddisfatte tutte queste condizioni, un ciclo di conteggio dei riferimenti impedirà che il controllo Win2D venga mai sottoposto alla pulizia della memoria. Le nuove risorse Win2D vengono allocate ogni volta che l'app si sposta a una pagina diversa, ma quelle precedenti non vengono mai liberate causando una perdita di memoria. Per evitare questo problema, è necessario aggiungere codice per interrompere in modo esplicito il ciclo.

Come risolverlo

Per interrompere il ciclo del conteggio dei riferimenti e lasciare che la pagina venga sottoposta a garbage collection:

  • Collegare l'evento Unloaded della pagina XAML che contiene il controllo Win2D
  • Nel gestore Unloaded, chiamare RemoveFromVisualTree sul controllo Win2D
  • Nel gestore Unloaded, rilasciare qualsiasi riferimento esplicito al controllo Win2D impostandolo su null.

Codice di esempio:

void page_Unloaded(object sender, RoutedEventArgs e)
{
    this.canvas.RemoveFromVisualTree();
    this.canvas = null;
}

Per esempi pratici, vedere una delle pagine demo della galleria di esempi .

Come individuare le perdite di ciclo

Per verificare se l'applicazione interrompe correttamente i cicli di conteggio dei riferimenti, aggiungere un metodo finalizzatore a qualsiasi pagina XAML che contiene controlli Win2D:

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

Nel costruttore App configurare un timer che assicurerà che l'operazione di Garbage Collection venga eseguita a intervalli regolari:

var gcTimer = new DispatcherTimer();
gcTimer.Tick += (sender, e) => { GC.Collect(); };
gcTimer.Interval = TimeSpan.FromSeconds(1);
gcTimer.Start();

Vai alla pagina, quindi naviga verso un'altra pagina. Se tutti i cicli sono stati interrotti, l'output Debug.WriteLine verrà visualizzato nel riquadro di output di Visual Studio entro uno o due secondi.

Si noti che la chiamata GC.Collect è dirompente e danneggia le prestazioni, quindi è consigliabile rimuovere questo codice di test non appena si completa il test per le perdite.

Dettagli macabri

Un ciclo si verifica quando un oggetto A ha un riferimento a B, allo stesso tempo B ha anche un riferimento a A. Oppure quando A fa riferimento a B e B fa riferimento a C, mentre C fa riferimento ad A e così via.

Quando ci si iscrive agli eventi di un controllo XAML, questo tipo di ciclo è praticamente inevitabile.

  • La pagina XAML contiene riferimenti a tutti i controlli contenuti all'interno di esso
  • I controlli contengono riferimenti ai delegati del gestore di eventi che sono stati associati ai relativi eventi
  • Ogni delegato mantiene un riferimento all'istanza di destinazione
  • I gestori di eventi sono in genere metodi di istanza della classe di pagina XAML, quindi i riferimenti all'istanza di destinazione puntano nuovamente alla pagina XAML, creando un loop.

Se tutti gli oggetti coinvolti vengono implementati in .NET, tali cicli non sono un problema perché .NET viene sottoposto a Garbage Collection e l'algoritmo di Garbage Collection è in grado di identificare e recuperare gruppi di oggetti anche se sono collegati in un ciclo.

A differenza di .NET, C++ gestisce la memoria in base al conteggio dei riferimenti, che non è in grado di rilevare e recuperare cicli di oggetti. Nonostante questa limitazione, le app C++ che usano Win2D non presentano problemi perché per impostazione predefinita i gestori eventi C++ contengono riferimenti deboli anziché riferimenti sicuri all'istanza di destinazione. Pertanto, la pagina fa riferimento al controllo e il controllo fa riferimento al delegato del gestore eventi, ma questo delegato non fa riferimento alla pagina in modo che non vi sia alcun ciclo.

Il problema si verifica quando un componente WinRT C++ come Win2D viene usato da un'applicazione .NET:

  • La pagina XAML fa parte dell'applicazione, quindi utilizza la raccolta dei rifiuti (Garbage Collection)
  • Il controllo Win2D viene implementato in C++, quindi usa il conteggio dei riferimenti
  • Il delegato del gestore eventi fa parte dell'applicazione, quindi usa Garbage Collection e contiene un riferimento sicuro all'istanza di destinazione

È presente un ciclo, ma gli oggetti Win2D che partecipano a questo ciclo non usano .NET Garbage Collection. Ciò significa che il Garbage Collector non è in grado di visualizzare l'intera catena, quindi non è in grado di rilevare o recuperare gli oggetti. In questo caso, l'applicazione deve contribuire interrompendo in modo esplicito il ciclo. Questa operazione può essere eseguita rilasciando tutti i riferimenti dalla pagina al controllo (come consigliato in precedenza) o rilasciando tutti i riferimenti dal controllo ai delegati dei gestori degli eventi che potrebbero fare riferimento alla pagina (usando l'evento Unloaded della pagina per annullare la sottoscrizione di tutti i gestori degli eventi).