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 tali controlli vengano recuperati dal Garbage Collector.

You have a problem if...

Se vengono soddisfatte tutte queste condizioni, un ciclo di conteggio dei riferimenti manterrà che il controllo Win2D non venga mai sottoposto a Garbage Collection. Le nuove risorse Win2D vengono allocate ogni volta che l'app passa a una pagina diversa, ma quelle precedenti non vengono mai liberate in modo che la memoria venga persa. Per evitare questo problema, è necessario aggiungere un codice per interrompere in modo esplicito il ciclo.

Come correggere

Per interrompere il ciclo del conteggio dei riferimenti e lasciare che la pagina venga sottoposto a Garbage Collection:

  • Associare Unloaded l'evento della pagina XAML che contiene il controllo Win2D
  • Nel Unloaded gestore chiamare RemoveFromVisualTree sul controllo Win2D
  • Nel Unloaded gestore rilasciare (impostando su null) tutti i riferimenti espliciti al controllo Win2D

Codice di esempio:

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

Per esempi funzionanti, vedere una delle pagine demo della raccolta di esempi.

Come testare le perdite di cicli

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 App costruttore 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();

Passare alla pagina, quindi allontanarla da un'altra pagina. Se tutti i cicli sono stati interrotti, verrà visualizzato Debug.WriteLine l'output nel riquadro di output di Visual Studio entro un secondo o due.

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

Dettagli gory

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 sottoscrivono 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 che sono stati sottoscritti ai relativi eventi
  • Ogni delegato contiene un riferimento all'istanza di destinazione
  • I gestori eventi sono in genere metodi di istanza della classe di pagina XAML, quindi i riferimenti all'istanza di destinazione puntano alla pagina XAML, creando un ciclo

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 usa 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 del gestore eventi che potrebbero puntare alla pagina (usando l'evento Unloaded della pagina per annullare la sottoscrizione di tutti i gestori eventi).