none
Form Mdi Child - Close() ou Dispose() RRS feed

  • Question

  • Bonjour,

    Sur un application MDI je suis en train de régler les problèmes de memory Leaks (en utilisant ANTS Memory Analyser).
    En faisant un petit projet de test je me suis rendu compte qu'une Form MDI rattachée a une Form MDI Container n'est pas libérée de la mémoire après un close(), pire si on a le malheur de faire un close() dans le Form MDI il n'est plus possible de faire un dispose correct par la suite... j'ai passé beaucoup de temps à chercher et je ne trouve rien de concluant.
    Beaucoup de monde à rencontré le même problème que moi mais aucune réponse valable n'est jamais fournie.
    Cela m'inquiète un peu et surtout me surprend, l'utilisation des Mdi Child est quand même répandue non ?

    mercredi 10 février 2010 16:02

Réponses

  • Bonjour,

    Ce problème est rapporté et les programmeurs vont l’analyser. Pour le moment, je vous conseille d’essayer mettre la propriété MdiParent du formulaire enfant au NULL avant d’appeler la méthode Close(). Vérifiez si vous avez encore le memory leak après ça.

     

    Cordialement,

    Alex


    Alex Petrescu - MSFT
    • Marqué comme réponse Alex Petrescu mercredi 17 février 2010 11:38
    lundi 15 février 2010 15:08
  • Bonjour,

    effectivement après la fermeture il reste une référence sur la fenêtre fille ( via ANTS memory profiler ). Si par contre on ouvre une nouvelle fenêtre, celle si sera libérée. Il semble donc que seule la dernière fenêtre ouverte reste référencée et pose problème : il y a d'ailleurs un bug sur Connect à ce sujet : voir ici

    J'ai également trouvé cette discussion qui propose une solution en surchargeant l'évènement OnMdiChildActivate sur la fenêtre MDI et passe la propriété FormerlyActiveMdiChild qui pose problème à null. ( après avoir effectué le test la fenêtre fille est bien libérée ).
    Néanmoins dans une majorité des cas avoir une seule instance non libérée devrait être acceptable et l'on doit pouvoir se passer de la surcharge.

    Cordialement.

    protected override void OnMdiChildActivate( EventArgs e )
    {
      base.OnMdiChildActivate( e );
      try
      {
        typeof( Form ).InvokeMember( "FormerlyActiveMdiChild",
          BindingFlags.Instance | BindingFlags.SetProperty |
          BindingFlags.NonPublic, null,
          this, new object[] { null } );
      }
      catch( Exception )
      {
        // Something went wrong. Maybe we don't have enough permissions
        // to perform this or the "FormerlyActiveMdiChild" property
        // no longer exists.
      }
    }


    Edit : d'autres liens sur ce problème sur msdn
    • Marqué comme réponse Alex Petrescu mercredi 17 février 2010 11:38
    lundi 15 février 2010 16:05
    Modérateur

Toutes les réponses

  • Bonjour,

     

    A partir de la documentation msdn pour la methode Form.Close() on trouve deux motifs pour lesquels un formulaire n’est pas disposé sur Close() :

    1. lorsqu'il fait partie d'une application MDI et que le formulaire n'est pas visible ;

    2. lorsque vous avez affiché le formulaire en utilisant ShowDialog.

    Dans ces circonstances, vous devez appeler manuellement  Dispose pour marquer tous les controles du formulaire pour le garbage collection 


    Pouvez-vous confirmer si une de ces situations s'applique a votre projet?

     

    Cordialement,

    Alex


    Alex Petrescu - MSFT
    • Proposé comme réponse Alex Petrescu jeudi 11 février 2010 09:19
    jeudi 11 février 2010 09:17
  • Bonjour et merci pour votre réponse.

    Non je ne suis dans aucun de ces cas... et c'est bien la le problème.
    En gros je fais quelques chose de très très simple.

     

    private void ShowNewForm(object sender, EventArgs e)

    {

    TEST_Form2 form= new TEST_Form2();

    form.MdiParent =
    this;

    form.Show();

    }

    dans ma TEST_Form2  j'ai un bouton qui fait un Close(). Rien de plus.
    Le test ce fait en 2 minutes dans un petit projet de test, la mémoire n'est pas libérée c'est un fait...
    Je suis complètement haluciné par un tel problème.
    Solution envisageable (testée et qui marche) mais qui est vraiment "moche".
    Faire un Dispose() à la place du Close() et intercepter le Disposed de la form dans le parent.
    Il faut ensuite faire un dispose du sender (donc de la form) dans les MdiChildren de la MDIParent.
    Autant dire que c'est de la bidouille et surtout problématique car on ne peut plus avoir accès aux Event déclenchés par le Close(), a savoir le OnClosing et OnClosed...

    Si vous avez une solution viable, je vous serais eternellement reconnaissant...!

     

    jeudi 11 février 2010 18:06
  • Bonjour,

    Ce problème est rapporté et les programmeurs vont l’analyser. Pour le moment, je vous conseille d’essayer mettre la propriété MdiParent du formulaire enfant au NULL avant d’appeler la méthode Close(). Vérifiez si vous avez encore le memory leak après ça.

     

    Cordialement,

    Alex


    Alex Petrescu - MSFT
    • Marqué comme réponse Alex Petrescu mercredi 17 février 2010 11:38
    lundi 15 février 2010 15:08
  • Bonjour,

    effectivement après la fermeture il reste une référence sur la fenêtre fille ( via ANTS memory profiler ). Si par contre on ouvre une nouvelle fenêtre, celle si sera libérée. Il semble donc que seule la dernière fenêtre ouverte reste référencée et pose problème : il y a d'ailleurs un bug sur Connect à ce sujet : voir ici

    J'ai également trouvé cette discussion qui propose une solution en surchargeant l'évènement OnMdiChildActivate sur la fenêtre MDI et passe la propriété FormerlyActiveMdiChild qui pose problème à null. ( après avoir effectué le test la fenêtre fille est bien libérée ).
    Néanmoins dans une majorité des cas avoir une seule instance non libérée devrait être acceptable et l'on doit pouvoir se passer de la surcharge.

    Cordialement.

    protected override void OnMdiChildActivate( EventArgs e )
    {
      base.OnMdiChildActivate( e );
      try
      {
        typeof( Form ).InvokeMember( "FormerlyActiveMdiChild",
          BindingFlags.Instance | BindingFlags.SetProperty |
          BindingFlags.NonPublic, null,
          this, new object[] { null } );
      }
      catch( Exception )
      {
        // Something went wrong. Maybe we don't have enough permissions
        // to perform this or the "FormerlyActiveMdiChild" property
        // no longer exists.
      }
    }


    Edit : d'autres liens sur ce problème sur msdn
    • Marqué comme réponse Alex Petrescu mercredi 17 février 2010 11:38
    lundi 15 février 2010 16:05
    Modérateur
  • Merci pour vos réponses. Je vais essayer vos solutions.

    Cordialement.

    mardi 16 février 2010 13:52
  • Ca ne marche pas correctement ni l'une ni l'autre.

    Mettre le Parent à Null avant de faire un close() libère la mémoire mais provoque un effect visuel hyper désagréable.
    La fenètre disparait bizarrement, francement dans une application pro, ca ne fait pas pro du tout...

    La seconde proposition ne change rien à mon probleme...

    Je reste ahuri devant un tel problème... personne ne travaille sur des projets MDI ???
    vendredi 19 février 2010 17:16
  • Bonsoir,

    c'est bien uniquement la première fenêtre qui est ouverte qui reste référencée, est ce bien toi aussi ce que tu constates ? si tu ouvres une nouvelle fenêtre la 1ére est bien libérée ?

    Pour ma part j'ai testé la solution consistant à mettre à jour la propriété FormelyActiveMdiChild par reflection et dans ce cas la mémoire est correctement libérée ( il n'y a plus de lien de dépendance visible dans ANTS ).

    La plus part du temps avoir une instance de plus ne pose pas vraiment de soucis je pense que c'est pour cela qu'il n'y a pas eu de correction pour le moment.
    Même si je conviens qu'il s'agit bien d'un problème, est-ce que cette instance est si volumineuse que dans ton cas cela pose un problème ?


    vendredi 19 février 2010 17:50
    Modérateur
  • Oui la première instance pour un type donnée, sauf que dans notre application nous avons énormément de fenetre mais toutes sont uniques...
    Bon j'ai du M****r quelque part je vais essayer à nouveau.

    protected override void OnMdiChildActivate( EventArgs e )
    {
      base.OnMdiChildActivate( e );
      try
      {
        typeof( Form ).InvokeMember( "FormerlyActiveMdiChild",
          BindingFlags.Instance | BindingFlags.SetProperty |
          BindingFlags.NonPublic, null,
          this, new object[] { null } );
      }
      catch( Exception )
      {
        // Something went wrong. Maybe we don't have enough permissions
        // to perform this or the "FormerlyActiveMdiChild" property
        // no longer exists.
      }
    }

    Cette procedure tu la mets bien dans la MDIParent ? (Ca parait logique, mais comme c'est ce que j'ai fait et que ca ne marche pas... je pose la question.)

    Merci pour l'aide.
    samedi 20 février 2010 08:46
  • Oui c'est bien dans le MDIParent, mais ce n'est pas la première instance pour un type donnée pour laquelle une dépendance subsiste mais bien la dernière instance quelque soit le type ( donc une seule instance référencée par la propriété FormerlyActiveMdiChild qui empêche le Garbage Collector de supprimer celle-ci de la mémoire )

    Est ce bien la même dépendance que tu observes ?

    Cordialement
    lundi 22 février 2010 09:52
    Modérateur
  • Salut,

    BOn je n'ai visiblement pas le même résultat que toi.
    En gros j'ai un projet avec un MDI Parent et une Form enfant.
    Dans le MDIParent j'ai ça :

     

    private void ShowNewForm(object sender, EventArgs e)

    {

     

    Form2 v = new Form2();

    v.MdiParent =

    this;

    v.Show();

    }

     

    protected override void OnMdiChildActivate(EventArgs e)

    {

     

    base.OnMdiChildActivate(e);

     

    try

    {

     

    typeof(Form).InvokeMember("FormerlyActiveMdiChild", BindingFlags.Instance | BindingFlags.SetProperty |

     

    BindingFlags.NonPublic, null,

     

    this, new object[] { null });

    }

     

    catch (Exception)

    {

     

    // Something went wrong. Maybe we don't have enough permissions

     

    // to perform this or the "FormerlyActiveMdiChild" property

     

    // no longer exists.

    }

    }


    Dans la Form enfant je n'ai qu'un bouton, ce bouton fait un Close() et c'est tout.

    1. Je lance le projet et je fais un "Take Mem Snapshot" dans le memory profiler
    2. Sur le MDIParent je clique deux fois sur l'ouverture de l'enfant, donc je me retrouve avec deux instances de la form enfant.
    3. Je ferme les deux instances
    4. Je fais un mem snapshot dans le memory profiler
    5. Dans la liste des instances disposées mais encore en mémoire j'ai mes deux instances de ma Form enfant.

    Si tu n'as pas la même chose je suis preneur...!!! mais j'ai du mal a croire qu'on obtienne pas le même résultat...

    Merci de ton aide.

    lundi 22 février 2010 10:32
  • Bonjour,

    ce que tu observes n'est pas un bug mais le comportement normal du garbage collector. Les instances disposées sont toujours présentes car le garbage collector n'a pas encore effectué un cycle pour libérer la mémoire.

    Si on effectue un test simple ( c'est à dire sans utilisé l'appel à FormelyActiveChildMDI )

    1. j'ouvre 3 fenêtres filles et je les ferme

    2. SnapShot : dans le snapshot on voit effectivement qu'il reste trois instances disposées et toujours en mémoire, simplement car le garbage collector n'est pas encore passé, c'est un comportement normal : la mémoire n'est pas libérée dès que la fenêtre est fermée, mais quand le garbage collector de par son fonctionnement et ses algorithmes juge utile d'effectuer une collecte.

    3. Le point important pour que la libération ait lieu est qu'il ne reste pas de référence qui empêche le garbage collector de pouvoir libérer l'objet. Sur ce point il existe donc un problème qui empêchera UNE des trois fenêtres filles ( et peu importe son type )  d'être libérée : le lien vers la fenêtre mère pour la dernière fenêtre ouverte . Pour le voir sur chacune des instances de fenêtre fille faire : show object rentention graph.
    - pour 2 fenêtres il n'existe aucun lien. Les fenêtres pourront donc être libérées normalement
    - pour une fenêtre on voit : System.Windows.Form.PropertyStore + ObjectEntry[]. Il s'agit du lien vers la propriété FormelyActiveChildMDI de la fenêtre MDI.

    Si tu as attends ou force la libération ( ce qui est généralement déconseillé via un GC.Collect() tu observeras qu'il ne restera qu'une instance )

    4. Si tu refais un test avec la méthode mentionnée sur FormelyActiveChildMDI tu constateras que pour toutes les forms cette fois, il n'existe aucun problème de dépendance et que donc le Gargage Collector pourra fonctionner. Ceci sauf cas particulier tu dois pouvoir t'en passer.


    Cordialement

    lundi 22 février 2010 13:26
    Modérateur
  • Oui j'observe la même chose, on est d'accord.
    Merci pour l'aide.

    mardi 23 février 2010 15:58