9
votes

Événements d'objet .Net et disposer / gc

EDIT: strong> Après Joel Coehoorns Excellente réponse, je comprends que je dois être plus précis, alors j'ai modifié mon code pour être plus proche de la chose que j'essaie de comprendre ...

Événements: Strong> Si je comprends bien, en arrière-plan, les événements sont "Collection" d'EventHandlers alias les délégués qui seront exécutés lors de la soulevée des événements. Donc, pour moi, cela signifie que si l'objet y strud> a l'événement e et objet x abonna à l'événement ye strong>, alors y fort> aura une référence à x, car y fort> doit exécuter la méthode située dans x, de cette façon, x ne peut pas être collecté fort>, et cette chose que je comprends. p> xxx pré >

mais ce n'est pas ce que Joel Coehoorn raconte ... strong> p>

Cependant, il existe un problème avec des événements tels que parfois les personnes aiment utiliser Idisposable avec des types qui ont des événements. Le problème est que lorsqu'un type X abonna aux événements dans un autre type Y, X a maintenant une référence à Y. Cette référence empêchera Y d'être collectée. P> BlockQuote>

Je ne comprends pas comment X STROR> Référencera le Y ??? P>

J'ai modifié un peu mon exemple pour illustrer davantage mon cas plus proche de: p>

public event EventHandler EventHappened
   {
      add 
      {
         eventTable["EventHappened"] = (EventHandler)eventTable["EventHappened"] + value;
      }
      remove
      {
         eventTable["EventHappened"] = (EventHandler)eventTable["EventHappened"] - value; 
      }
   }


3 commentaires

Dupliqué possible de Supprimer les gestionnaires sur la disposition de l'objet


Ce n'est pas un duplicata de cette question.


quelle est la question? Vous avez trop d'édits, donc je ne peux pas comprendre quel est le sujet de la discussion. Veuillez ajouter des modifications sur le bouton de la question et ne supprimez pas la question initiale.


7 Réponses :


1
votes

J'aurais que ma classe B implémente Idisposable aussi bien et dans sa mise à disposition de la routine, je vérifierais d'abord si A n'est pas nulle puis disposera de disposer A. En utilisant cette approche, vous devez simplement vous assurer de disposer de la dernière de votre classe. et les internes géreront tous les autres disposent.


0 commentaires

13
votes

La durée de vie de l'objet B est plus longue que A, donc A peut être disposé plus tôt

On dirait que vous êtes confus "disposition" avec "collection"? Disposer d'un objet a rien à faire avec la mémoire ou la collecte des ordures. Pour vous assurer que tout est clair, rompons les deux scénarios, puis je passerai à des événements à la fin:

Collection:

rien vous permettrez de collecter de manière à être collectée avant son parent B. Tant que B est accessible , est donc A. même si A est A. Même si A est privé, c'est toujours accessible à partir de n'importe quel code dans B, et aussi longtemps que B est accessible, A est considéré comme accessible. Cela signifie que le collecteur des ordures ne sait pas avec certitude que vous avez terminé avec elle et ne collectionnerai jamais un problème jusqu'à ce qu'il soit aussi sûr de collecter B. Ceci est vrai même si vous appelez explicitement GC.Collect () ou similaire. Comme un objet est accessible, il ne sera pas collecté.

Élimination:

Je ne suis même pas sûr pourquoi vous êtes implémenté Idisposable ici (il a rien à faire avec la mémoire ou la collection de déchets), mais je vais vous donner l'avantage du doute pour le moment où Nous ne voyons tout simplement pas la ressource non gérée.

Rien ne vous empêche de disposer d'un quand vous voulez. Il suffit d'appeler a.dispose (), et c'est fait. La seule façon dont le .NET Framework appellera jamais Disposer () pour vous automatiquement est à la fin de en utilisant le bloc . Disposer () n'est pas appelé lors de la collecte des ordures, sauf si vous le faites dans le cadre du finaliseur de l'objet (plus sur les finaliseurs dans un instant).

Lors de la mise en œuvre Idisposable, vous envoyez un message aux programmeurs que ce type devrait (peut-être même "DO" doit ") être disposé rapidement. Il existe deux modèles corrects pour tout objet isisposable (avec deux variations du motif). Le premier modèle consiste à encercler le type lui-même dans un bloc d'utilisation. Lorsque cela n'est pas possible (par exemple: le code tel que le vôtre où le type est un élément d'un autre type), le deuxième motif est que le type parent doit également être mis en œuvre isisposable, de sorte qu'il peut alors être inclus dans un bloc d'utilisation, et c'est Disposer () peut appeler le dispositif de votre type (). La variation de ces modèles consiste à utiliser Essayer / enfin des blocs au lieu d'un bloc d'utilisation, où vous appelez disposer () dans le bloc enfin.

maintenant sur les finaliseurs. La seule fois que vous devez implémenter un finaliseur est pour un type isisposable qui l'origine une ressource non gérée. Ainsi, par exemple, si votre type A ci-dessus enveloppe une classe comme SQLConnection, il n'a pas besoin d'un finisseur car le finaliseur de SQLConnection elle-même s'occupera de tout nettoyage nécessaire. Mais si votre type A impliquait une connexion à un tout nouveau type de moteur de base de données, vous voudriez qu'un finisseur de faire sûr vos connexions sont fermées lorsque l'objet est collecté. Cependant, votre type B n'aurait pas besoin d'un finisseur, même s'il gère / enveloppe votre type A, car le type A s'occupe de la finalisation des connexions.

Événements:

Techniquement, les événements sont toujours gérés du code et ne doivent pas nécessairement être disposés. Cependant, il existe un problème avec des événements tels que parfois les personnes aiment utiliser Idisposable avec des types qui ont des événements. Le problème est que lorsqu'un type X abonna aux événements dans un autre type Y, Y a maintenant une référence à X. Cette référence peut empêcher X d'être collectés. Si vous attendez que Y ait une durée de vie plus longue, vous pouvez rencontrer des problèmes ici, en particulier si y est très vécu par rapport à de nombreux X qui viennent et passez au fil du temps.

Pour contourner cela, parfois les programmeurs auront de type y Implément Idisposable, et le but de la méthode de Dispose () est de vous désabonner de tout événement afin que l'abonnement des objets puisse également être collecté. Techniquement, ce n'est pas le but de la tendance (), mais cela fonctionne assez bien que je ne vais pas discuter à ce sujet. Il y a deux choses que vous devez savoir lors de l'utilisation de ce modèle avec des événements:

  1. Vous n'avez pas besoin d'un finisseur s'il s'agit de la seule raison de la mise en œuvre Idisposable
  2. Les instances de votre type ont toujours besoin d'une utilisation ou d'un blocage enfin / enfin, ou vous n'avez rien gagné. Sinon disposer () ne sera pas appelé et vos objets ne peuvent toujours pas être collectés.

    Dans ce cas, votre type A est privé pour taper B, et seul le type B ne peut abonner aux événements d'A. Comme "A" est membre de type B, ni éligible à la collecte des ordures que B n'est plus accessible, à quel point les deux ne seront plus accessibles et que la référence d'abonnement à l'événement ne comptera pas. Cela signifie qu'une référence détenue sur B par un événement d'un empêcherait d'être collectée. Toutefois, si vous utilisez un type A dans d'autres endroits, vous pouvez toujours souhaiter une implémente Idisposable pour vous assurer que vos événements sont désabonnés. Si vous faites cela, assurez-vous de suivre le motif entier, de sorte que les instances d'A sont enfermées dans l'utilisation ou l'essai / enfin de blocs.


4 commentaires

Je pensais que si X souscrivez un événement de Y, Y prend une référence à X, pas vice-versa.


Oui, je pense que je l'ai reculé ici. Peut avoir besoin de mettre à jour, mais je vais devoir attendre de l'examiner davantage jusqu'à ce que le travail ralentit ... peut-être tard ce soir.


Si y publie un événement, et X abonna à cet événement, y obtient une référence à X (à travers la multicastdélégate derrière). Mais sans avoir une référence à Y, comment X peut-on abonner à l'événement de Y? Est-ce que je fais une erreur stupide ici?


@Nero voir dans ma question Modifier. X a reçu y dans le constructeur, abonne à l'événement de Y puis ne pas stocker de référence à y interne.



0
votes

Vous n'êtes pas besoin pour décrocher des gestionnaires d'événements lors de la mise en disposition d'un objet, bien que vous puissiez veux . Par là, je veux dire que le GC nettoyera des manipulateurs d'événements tout simplement bien sans aucune intervention de votre part, cependant, selon le scénario, vous souhaiterez peut-être supprimer ces gestionnaires d'événements avant que le GC ne puisse empêcher le manipulateur d'être appelé quand vous n'étaient pas appelés. t en attendant.

Dans votre exemple, je pense que vos rôles inversés - Classe A ne doivent pas vraiment vous désabonner des gestionnaires d'événements ajoutés par d'autres et n'ont pas de réel besoin de supprimer les gestionnaires d'événements, comme Il peut plutôt juste arrêter d'élever ces événements!

supposons cependant que la situation est inversée xxx

si elle est possible pour A Pour continuer à augmenter les événements même après B a été disposé, et le gestionnaire d'événements pour B pourrait faire une exception ou un autre comportement imprévu si B est éliminé alors sa bonne idée de se désabonner de cet événement en premier.


1 commentaires

Non, je n'ai pas de rouleaux inversés, j'ai modifié ma question pour être plus claire.



4
votes

Contrairement à ce que de nombreuses autres réponses réclament, événements dont la vie de GC de l'éditeur peut dépasser la durée de vie d'un souscripteur doit être considérée comme des ressources non gérées . Le terme "non géré" dans la phrase "ressource non gérée" ne signifie pas "complètement en dehors du monde du code géré", mais concerne plutôt si des objets nécessitent un nettoyage au-delà de celui fourni par le collecteur de déchets géré.

Par exemple, une collection peut exposer un événement Collection> Collection>. Si des objets d'un autre type qui souscrit à un tel événement sont créés et abandonnés à plusieurs reprises, la collection peut finir par détenir une référence déléguée à chaque objet de ce type. Si cette création et cette abandon se produisent. Une fois par seconde (comme cela pourrait arriver si l'objet en question a été créé dans une routine qui met à jour une fenêtre d'interface utilisateur), le nombre de telles références pourrait augmenter de plus de 86 000 pour chaque jour que le programme était en cours d'exécution. Pas un gros problème pour un programme qui ne fonctionne jamais plus de quelques minutes, mais un tueur absolu pour un programme pouvant être couru pendant des semaines à la fois.

Il est vraiment malheureux que Microsoft ne soit pas proposé avec un meilleur motif de nettoyage d'événement dans VB.NET ou C #. Il y a rarement une raison pour laquelle une instance de classe qui souscrit aux événements ne devrait pas les nettoyer avant d'abandonner, mais Microsoft n'a rien fait pour faciliter un tel nettoyage. En pratique, on peut s'en tirer avec des objets abandonnés souscrits à des événements suffisamment souvent (car l'éditeur d'événements ira à peu près au même moment que l'abonné) que le niveau d'effort gênant nécessaire pour assurer des événements correctement nettoyés Ne semble pas utile. Malheureusement, il n'est pas toujours facile de prédire tous les cas où un éditeur d'événement pourrait vivre plus longtemps que prévu; Si de nombreuses classes laissent des événements pendants, il est possible que d'énormes quantités de mémoire soient irrécouvrables, car l'un des abonnements d'événements appartient à un objet de longue durée.

addendum en réponse à Modifier

si x devait s'abonner à un événement à partir de y , puis abandonnez toutes les références à y , et si y est devenu admissible à la collecte, x n'empêcherait pas y d'être collecté. Ce serait une bonne chose. Si x devait conserver une référence forte à y dans le but de pouvoir en disposer, une telle référence empêcherait y d'être collecté . Cela pourrait sans doute ne pas être une si bonne chose. Dans certaines situations, il serait préférable de x pour garder un long affaiblie (un construit avec le deuxième paramètre défini sur true ) sur Y plutôt qu'une référence directe; Si la cible du faiblicielle est non nulle lorsque x est Dispose d, il devra se désabonner de y L'événement. Si la cible est NULL, elle ne peut pas se désabonner, mais cela n'aura pas d'importance, car par alors y (et sa référence à x ) aura cessé d'exister. Notez que dans l'événement improbable que y meurt et est ressuscité, x voudra toujours vous désabonner de son événement; en utilisant un long affaiblie s'assurera que cela peut toujours arriver.

tandis que certains affirmeraient que x ne devrait pas déranger la référence à une référence à y , et y doit simplement être écrit pour utiliser une sorte de l'expédition de l'événement faible, ce comportement n'est pas correct dans le cas général car il n'y a aucun moyen pour y de savoir si x ferait tout ce que l'autre code pourrait se soucier de Même si y détient la seule référence à x . Il est tout à fait possible que x peut contenir une référence à un objet fortement enraciné et peut faire quelque chose à cet autre objet dans son gestionnaire d'événements. Le fait que y détient la seule référence à x ne doit pas impliquer qu'aucun autre objet n'est "intéressé" dans x . La seule solution généralement correcte consiste à avoir des objets qui ne sont plus intéressés par les événements d'autres objets en avérant ces derniers objets de ce fait.


0 commentaires

1
votes

Référence MSDN

"Pour éviter que votre gestionnaire d'événements soit invoqué lorsque l'événement est soulevé, désabonnez-vous à l'événement. Afin de prévenir les fuites de ressources, vous devez vous désabonner des événements avant de disposer d'un objet d'abonné. Jusqu'à ce que vous vous désabonnez-vous d'un événement, Le délégué de multidiffusion qui sous-tend l'événement dans l'objet de publication a une référence au délégué qui encapsule le gestionnaire d'événements de l'abonné. Tant que l'objet de publication détient cette référence, la collecte des ordures ne supprimera pas votre objet d'abonné. "

"Lorsque tous les abonnés se sont désabonnés à partir d'un événement, l'instance d'événement dans la classe Editeur est définie sur NULL."


1 commentaires

Le problème est que je ne dispose pas de l'abonné, je dispose de l'éditeur (l'abonné continue d'exister), en outre, l'abonné n'a pas référence à l'éditeur en interne.



0
votes

L'objet Une références B via EventHandler Délégate (A a une instance d'EventHandler Wich References B). B Vous n'avez aucune référence à A. Lorsque A est défini sur NULL, il sera collecté et la mémoire sera libérée. Donc, vous n'avez pas besoin d'effacer quoi que ce soit dans ce cas.


0 commentaires

5
votes

J'ai ajouté mes commentaires dans votre exemple de code.

class MainClass
{
    public static Publisher Publisher;

    static void Main()
    {
        Publisher = new Publisher();

        Thread eventThread = new Thread(DoWork);
        eventThread.Start();

        Publisher.StartPublishing(); //Keep on firing events
    }

    static void DoWork()
    {
        var subscriber = new Subscriber();
        subscriber = null; 
        //Subscriber is referenced by publisher's SomeEvent only
        Thread.Sleep(200);
        //We have waited enough, we don't require the Publisher now
        Publisher = null;
        GC.Collect();
        //Even after GC.Collect, publisher is not collected even when we have set Publisher to null
        //This is because 'StartPublishing' method is under execution at this point of time
        //which means it is implicitly reachable from Main Thread's stack (through 'this' pointer)
        //This also means that subscriber remain alive
        //Even when we intended the Publisher to stop publishing, it will keep firing events due to somewhat 'hidden' reference to it from Main Thread!!!!
    }
}

internal class Publisher
{
    public void StartPublishing()
    {
        Thread.Sleep(100);
        InvokeSomeEvent(null);
        Thread.Sleep(100);
        InvokeSomeEvent(null);
        Thread.Sleep(100);
        InvokeSomeEvent(null);
        Thread.Sleep(100);
        InvokeSomeEvent(null);
    }

    public event EventHandler SomeEvent;

    public void InvokeSomeEvent(object e)
    {
        EventHandler handler = SomeEvent;
        if (handler != null)
        {
            handler(this, null);
        }
    }

    ~Publisher()
    {
        Console.WriteLine("I am never Printed");
    }
}

internal class Subscriber
{
    public Subscriber()
    {
        if(MainClass.Publisher != null)
        {
            MainClass.Publisher.SomeEvent += PublisherSomeEvent;
        }
    }

    void PublisherSomeEvent(object sender, EventArgs e)
    {
        if (MainClass.Publisher == null)
        {
            //How can null fire an event!!! Raise Exception
            throw new Exception("Booooooooommmm");
            //But notice 'sender' is not null
        }
    }
}


3 commentaires

Merci! Vous avez fait presque toutes les choses sur les événements clairs pour moi :) Mais si j'utilise Ajout / Supprimer et gérer les gestionnaires de Hashtable, comment je les élever? Régulièrement, j'ai fait si (événement! = null) événement (..). Comment "cartographier" l'événement pour corriger le gestionnaire de hashtable?


Subscrits EventHandler = (EventHandler) Evénements éventuels ["EventHappened"]; Si (abonnés! = null) abonnés (ceci, evenarg.empty);


Une petite correction, avant de récupérer les abonnés du dictionnaire, vous devriez vérifier si la clé "eventhappened" existe ou non. À propos de la gestion d'un dictionnaire d'événement EventHandlers uniquement si votre classe expose un grand nombre d'événements sur lesquels il ne devait que quelques-uns ne devraient être souscrits à n'importe quel point (comme WinForms Controls), ce qui permet au compilateur de générer un support par défaut. Le délégué pour chacun des événements entraînerait un gaspillage de mémoire pour les objets de classe lorsque, en fait, il n'a pas besoin de toute cette mémoire.