IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Windows Forms Discussion :

[c#]Destructeur, problème quand je quitte de programme


Sujet :

Windows Forms

  1. #21
    Membre émérite
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    547
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 547
    Par défaut
    J'ajouterai juste au poste de SaumonAgile, sur les generations. En moyenne, compter une collecte Gen0 toutes les 5 secs, et multiplier par 10 pour chaque gen au dessus. De fait, si un objet de gen 2 doit etre finalizé, le temps de liberation effective peut etre tres long.

    Pour les sources, j'avoue preferer les bouquins (la en l'occurence ca venait de Essential .Net, volume 1 : The CLR), mais tu trouveras des articles interressants sur la msdn.

    Disons pour resumer, qu'il peut arriver d'avoir à implementer un finalizer, mais qu'a chaque fois que cette obligation apparait, le Dispose doit l'etre aussi, ce qui permet si l'utilisateur, dans le cas d'une lib, ou le developpeur utilise correctement leurs disposes de diminuer le surcout induit des finalizers, tout en assurant une liberation garantie aux ressources tenues. Bref, en simplifiant à l'extreme, Dispose() doit etre considéré comme le destructeur en .Net, et machin.Dispose() correspond conceptuellement à delete machin;.

    A noter enfin, ca peut etre etait deja dit, mais mieux vaut le savoir, qu'il ne faut jamais toucher à un membre managé dans un finalizer (le thread du finalizer invoquant les finalize dans un ordre non determiné, on peut obtenir des resultats surprenants).

    Le GC passera toujours derriere toi, mais le facteur temps peut etre grandement optimisé. Sans plus de precautions, tu peux vite te retrouver avec des consos memoires en dents de scie (dues à des promotions incessantes), alors que tu pourrais avoir une conso beaucoup plus stable.

    Le GC de .Net est une sacré machinerie, mais pour paraphraser un post d'un ng anglais, "ce n'est pas parce que tu as une femme de menage, qu'il faut se sentir obligé de mettre deux fois plus le souk." =p

  2. #22
    Membre éprouvé
    Avatar de NiamorH
    Inscrit en
    Juin 2002
    Messages
    1 309
    Détails du profil
    Informations forums :
    Inscription : Juin 2002
    Messages : 1 309
    Par défaut
    Ok merci pour ces infos intéressantes, j'irai approfondir dès que possible.

    Une question plus pratique maintenant, sur les consequences de tout ça.

    Imaginons une fonction potentiellement appelée par deux thread concurents. Le code de la fonction est critique, il ne doit pas être éxécuté parallèlement par plus d'un thread.

    En C++, j'utilise donc une section critique membre de ma classe dédiée à la fonction et un système de lock. Cela ressemble à peu près à ceci :

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void FonctionCritique()
    {
      // Verrouille l'entrée de la fonction
      Lock lock( m_sectionCritique );
     
      // Instructions
     
      if ( /* Test */ )
        return; // Sortie possible 1
     
      // Instructions
     
    } // Sortie possible 2

    L'objet Lock est écrit de telle sorte qu'il demande l'accès à la section critique dans son constructeur et libère cette dernière dans son destructeur.

    Ici, quelque soit le chemin d'éxécution du code et surtout le retour utilisé (1 ou 2), le destructeur de Lock sera appelé et la section critique libérée.

    L'utilisation du système est donc très léger et robuste car il garanti que la libération est faite, même en cas de levée d'éxception.

    En C#, j'ai l'impression que le système va devenir :
    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    void FonctionCritique()
    {
      // Verrouille l'entrée de la fonction
      Lock lock = new Lock( m_sectionCritique );
     
      using ( lock )
      {
        // Instructions
     
        if ( /* Test */ )
          return; // Sortie possible 1
     
        // Instructions
     
      } // Libère la section critique
     
    } // Sortie possible 2

    C'est déjà plus lourd, un niveau d'indentation supplémentaire, sans compter que ce n'est pas forcément le seul objet qui fonctionne de la sorte et je n'ai pas trouvé le moyen d'utiliser un seul using pour plusieurs objet de type différents.

    Alors comment faites vous ?

  3. #23
    Membre émérite
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    547
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 547
    Par défaut
    Tout simplement :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    lock(m_sectionCritique)
    {
    // code locké
    }
    Non ?

  4. #24
    Membre éprouvé
    Avatar de NiamorH
    Inscrit en
    Juin 2002
    Messages
    1 309
    Détails du profil
    Informations forums :
    Inscription : Juin 2002
    Messages : 1 309
    Par défaut
    Merde. Effectivement, lock est un mot clef dans csharp et je ne le savais pas. Ok il s'utilise comme tu l'as dit, mon exemple est donc très mal choisi.

    Mais tu as bien compris le mécanisme et mon souci, non ? Essaye d'imaginer le problème pour un objet qui fasse autre chose.

  5. #25
    Membre émérite
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    547
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 547
    Par défaut
    Je pense avoir compris le probleme (si je dis encore des conneries, n'hesites pas à mieux m'expliquer =p), et je me permets d'insister, le mot clé lock est ce que tu cherches. En C#, lock(truc) est equivalent à :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    try
    {
    Monitor.Enter(truc);
     
    // blabla
    }
    finally
    {
    Monitor.Exit(truc);
    }
    De fait, quel que soit le chemin de sortie (return multiple, exception levée), ton mutex sera deverouillé à un moment ou un autre. L'usage etant de locker sur un object static privé (eviter le lock(this) ) ne servant qu'à ca :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Truc
    {
        static object s_syncLock = new object();
     
        public void MaMethodeCritique()
        {
            lock(s_syncLock)
            {
                //Mon code critique
            }
        }
     
    }
    En esperant avoir compris. =p

  6. #26
    Expert confirmé
    Avatar de smyley
    Profil pro
    Inscrit en
    Juin 2003
    Messages
    6 270
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2003
    Messages : 6 270
    Par défaut
    Petit détail à propos des locks (encore my 2 cents), si on lock plusieurs objets il faut toujours le faire dans le même ordre.
    Soit 2 objets a et b, que l'on veut locker ()
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
    void truc1()
    {
     lock(a)
     {
       lock(b)
       {
         QuelqueChose
       }
     }
    }
     
    void truc2()
    {
     lock(b)
     {
       lock(a)
       {
         QuelqueChose
       }
     }
    }
    Le code précédent peut fonctionner, mais peut crasher aussi. En fait il peut se passer ceci :
    truc2 : lock(b)
    truc1 : lock(a)
    truc2 : lock(a) -> attend que truc1 ai fini avec
    truc1 : lock(b) -> attend que truc2 ai fini avec
    Dead lock ...

    On doit donc toujours faire attention à locker les resources dans le même ordre sinon on va à l'avant de blocages complètement aléatoires ...

  7. #27
    Membre éprouvé
    Avatar de NiamorH
    Inscrit en
    Juin 2002
    Messages
    1 309
    Détails du profil
    Informations forums :
    Inscription : Juin 2002
    Messages : 1 309
    Par défaut
    Houla, la discution part vers les techniques du locking, ce n'était pas du tout ce que je voulais mettre en avant!

    Mon problème est de retrouver l'élégance du code C++, en C# (pour une fois que c'est dans ce sens!).

    Cette élégance vient de l'utilisation des destructeurs en C++.
    Si j'instancie statiquement un objet dans une fonction (sans utiliser le mot clef new), l'objet est détruit à la fin de la fonction et le destructeur de l'objet est appelé.

    Imaginons, je ne sais pas moi, n'importe quel cas, pas forcément du thread locking. Tiens : je veux afficher une icone ou lancer une animation lorsque j'entre dans une fonction et cacher l'animation dès que j'en sors.

    L'approche objet dirait :
    - Un objet Animation qui agirait en suivant le modèle d'automate/machine à état.
    - Une méthode Demarrer() et Arreter()
    - A la construction doit être donné le chemin vers l'icone et l'état initial (si état == Animé alors Demarrer())
    - A la destruction, l'automate doit être arrêté si il se trouve encore dans l'état animé

    En C++, je peux écrire simplement :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    void Fun()
    {
      Animation animation( "image.gif", Animation::Anime );
      // faire plein d'autres trucs
    }
    En sortie de ma fonction, l'objet animation est détruit et son destructeur ~Animation() appelé. Je suis donc assuré que mon animation sera bien arrêtée dès que ma fonction retourne.

    En C#, si je me calque sur le même système, utilisant le destructeur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    void Fun()
    {
      Animation animation = new Animation( "image.gif", Animation::Anime );
      // faire plein d'autres trucs
    }
    Je ne sait pas quand l'objet animation est détruit et son destructeur ~Animation() appelé. Je suis donc pas sûr que mon animation sera bien arrêtée juste après que ma fonction ait retourné.

    Alors vous allez me faire deux remarques. La première c'est que ce n'est pas très grave si l'animation continue un peu après que la fonction ait retourné. Oui c'est vrai dans ce cas, on s'en fout un peu. Je n'ai pas trouvé d'exemples plus parlant, où cela pourrait vraiment avoir une importance, mais c'est certain qu'il existe des cas où cela en a.
    La deuxième c'est qu'il existe Dispose() combiné avec using (si multiples sorties possibles). Ok mais que se passe-t-il si j'ai 1 animation à lancer, 1 son à jouer et 1 chronomètre à lancer ? Je suis obligé de faire :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    void Fun()
    {
      Animation animation = new Animation( "image.gif", Animation::Anime );
      Son       son       = new Son      ( "son.wav",   Son::Joue );
      Chrono    chrono    = new Chrono   ( Chrono::Lance );
     
      using ( animation )
      {
        using ( son )
        {
          using ( chrono )
          {
            // faire plein d'autres trucs
          }
        }
      }
    }
    Ok, ici j'ai bien la garantie que mon animation, mon son et mon chrono sont stoppés à la fin de la fonction si j'ai bien implémenté Dispose()
    Mais le code n'est pas beau ! 3 indentations superflues.

  8. #28
    Expert confirmé
    Avatar de smyley
    Profil pro
    Inscrit en
    Juin 2003
    Messages
    6 270
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2003
    Messages : 6 270
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     
    void Fun()
    {
      Animation animation = new Animation( "image.gif", Animation::Anime );
      Son       son       = new Son      ( "son.wav",   Son::Joue );
      Chrono    chrono    = new Chrono   ( Chrono::Lance );
     
      try
      {
        /* faire d'autre trucs */
      }
      finally
      {
        animation.Dispose();
        son.Dispose();
        chrono.Dispose();
      }
    }
    Ce qui est dans finally sera toujours appelé, même s'il y a un crash monstrueux dans try (mais pas en cas de StackOverflow quand même ...)

  9. #29
    Membre émérite
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    547
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 547
    Par défaut
    Ok, je comprends mieux, desolé pour la meprise.

    Et pour faire, reproduire la facon C++y de gerer les scopes, non ce n'est pas possible, puisque les classes hors scope sont detruites quand le GC veut, et que les structures n'ont pas le droit de redefinir finalize.

    La methode de Smyley me parait la plus elegante (ou du moins la plus proche de ce que tu cherches).

+ Répondre à la discussion
Cette discussion est résolue.
Page 2 sur 2 PremièrePremière 12

Discussions similaires

  1. Réponses: 8
    Dernier message: 23/03/2006, 19h30
  2. Conserver des valeurs quand on ferme le programme
    Par Yepazix dans le forum Langage
    Réponses: 1
    Dernier message: 05/02/2006, 15h59
  3. [Debutant] Problème de fraction dans un programme
    Par SAKDOSS dans le forum Débuter
    Réponses: 4
    Dernier message: 22/10/2005, 18h38
  4. Problème installation SQL Server 2000 (programme antérieur)
    Par 404Found dans le forum MS SQL Server
    Réponses: 2
    Dernier message: 25/04/2005, 10h24
  5. Réponses: 1
    Dernier message: 16/05/2004, 17h56

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo