Windows.Forms, ToolStrip and Memory Leak


In questi giorni ho dovuto fare memory profiling di una grossa applicazione gestionale. Per farlo ho utilizzato un tool commerciale che ritengo molto buono. (Red Gate ANTS Memory Profiler).

Dopo un po' di tempo passato ad analizzare i dati quali Live Instance, Instance difference, Live size, Istance retention graph eccettera, ho scoperto nell'applicazione un memory leak causato da quelle form che utilizzavano il controllo Windows Form ToolStrip (vedi Classe ToolStrip su MSDN) conseguenza di un evento globale, in cui il controllo si "installa" per gestire il suo layout, come si può vedere da questo retention graph visualizzato da ANTS Memory Profiler. (Nota: Il grafico è realizzato su un form di esempio FormWithToolStrip creato per analizzare il problema)

 

Cercando su Internet ho trovato questo post di stackoverflow ToolStrip memory leak che ne spiega la ragione, indicando come soluzione quella di chiamare il metodo Dispose() sul controllo ToolStrip per permettere la deregistrazione di questi eventi.

 

Quello a cui bisogna stare molto attenti è quando il form, facendo parte di una applicazione MDI, viene aperto come modale chiamando il metodo Form.ShowDialog() (vedi Form.Close Method (Remarks) su MSDN) in questo caso il nostro form non viene finalizzato creando potenziali memory leak se oggetti ancora vivi o statici lo referenziano direttamente o indirettamente attraverso un controllo contenuto.

 

Perciò una soluzione è quella di includere il ciclo di vita del Form in un blocco using(...){}, in alternativa chiamare il metodo Dispose() (aggiungerei o Close()) alla fine dell'utilizzo del form modale.

author: Marco Baldessari | posted @ lunedì 1 gennaio 0001 00:00 | Feedback (0)

Garbage Collector - Collect completa e deterministica ...


Ho avuto la necessità di scrivere un oggetto che gestisca in modo deterministico il ciclo di vita un oggetto aggregato, che deve essere finalizzato in modo opportuno.

Per raggiungere questo obiettivo ho utilizzato il pattern IDisposable (vedi Implement IDisposable Correctly), poi ho scritto un test per verificare che l'oggetto quando finalizzato attraverso IDisposable (cioè quando usato in un blocco using(...) { }), finalizzasse a sua volta correttamente l'oggetto aggregato.

Volevo però verificare anche che la stessa operazione di finilazzazione avvenisse quando l'oggetto è rilasciato dal Garbage Collector, così ho dovuto trovare un metodo per forzare in modo deterministico e sincrono la finalizzazione degli oggetti nel GC. Ho scritto quindi una semplice funzione che effettua questa operazione chiamando in modo opportuno metodi statici della classe GC.

    public static class UtilsGarbageCollector

    {

        public static void ForceCompleteCollectAndWait()

        {

            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);

            GC.WaitForPendingFinalizers();

        }

    }

Ho scritto poi un test specifico per verificare le aspettive di questa funzione.

    [TestClass]

    public class TestFullSyncronizedCollect

    {

        [TestMethod]

        public void ShouldDeterministicCollectAllNotReferencedObject()

        {

            for (int i = 0; i < 100; i++)

            {

                new ClassA();

                new ClassB();

                new ClassC();

                new ClassD();

                new ClassE();

            }

 

            UtilsGarbageCollector.ForceCompleteCollectAndWait();

 

            Assert.AreEqual(0, BaseInstanceCounter.ClassInstance);

        }

    }

Dove le classi ClasseA...E sono sottoclassi di una particolare classe che conta le instanze "vive e non finalizzate".

    public class BaseInstanceCounter

    {

        public static int ClassInstance = 0;

 

        public BaseInstanceCounter()

        {

            ClassInstance++;

        }

 

        ~BaseInstanceCounter()

        {

            ClassInstance--;

        }

    }

 

    public class ClassA : BaseInstanceCounter { }

    public class ClassB : BaseInstanceCounter { }

    public class ClassC : BaseInstanceCounter { }

    public class ClassD : BaseInstanceCounter { }

    public class ClassE : BaseInstanceCounter { }

Se commentate nel test la chiamata al metodo UtilsGarbageCollector.ForceCompleteCollectAndWait(); questo non passa. (...è corretto dire quasi sempre non essendo deterministico il momento di finalizzazione degli oggetti da parte del GC)

author: Marco Baldessari | posted @ lunedì 1 gennaio 0001 00:00 | Feedback (0)