.NET 对象事件和处置/GC [英] .NET object events and dispose / GC

查看:19
本文介绍了.NET 对象事件和处置/GC的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 Joel Coehoorns 出色的回答之后,我明白我需要更具体,所以我修改了我的代码以更接近我想要理解的东西......

事件: 据我了解,在后台,事件是 EventHandlers 又名委托的集合",当事件引发时将被执行.所以对我来说,这意味着如果对象 Y 有事件 E 并且对象 X 订阅了事件 YE,那么 Y 将引用 X,因为Y必须执行位于X的方法,那样X不能被收集,那我明白了.

//在a中创建对这个(b)的引用.a.EventHappened += new EventHandler(this.HandleEvent);

但这不是 Joel Coehoorn 所说的……

<块引用>

但是,事件存在一个问题,即有时人们喜欢将 IDisposable 与具有事件的类型一起使用.问题是当一个类型 X 订阅另一个类型 Y 的事件时,X 现在有一个对 Y 的引用.这个引用会阻止 Y 被收集.

我不明白 X 将如何引用 Y ???

我对我的例子做了一些修改,以更接近地说明我的情况:

class Service//假设是windows服务,必须24/7在线{一个_a;无效开始(){CustomNotificationSystem.OnEventRaised += new EventHandler(CustomNotificationSystemHandler)_a = 新的 A();B b1 = 新 B(_a);B b2 = 新 B(_a);C c1 = 新 C(_a);C c2 = 新 C(_a);}无效 CustomNotificationSystemHandler(args){//_a.Dispose();由**编辑2***添加a.处置();_a = 新的 A();/*b1,b2,c1,c2 将继续存在,我知道他们现在会订阅到 _a 的前一个实例,我可以,但是在那个例子中,现在,没有人引用 _a 的前一个实例(b 不包含对 _a 的引用)并由我的理论上,以前的 _a 实例,现在可能会被收集……或者我失踪了某物???*/}}A 类: IDisposable{公共事件 EventHandler EventHappened;}B类{public B(A a)//B 类内部不存储对 a 的引用.{a.EventHappened += new EventHandler(this.HandleEventB);}公共无效HandleEventB(对象发送者,EventArgs args){}}C类{public C(A a)//Class B 不在内部存储对 a 的引用.{a.EventHappened += new EventHandler(this.HandleEventC);}公共无效HandleEventC(对象发送者,EventArgs args){}}

编辑 2: 好的,现在很清楚,当订阅者订阅发布者事件时,会在订阅者中创建对发布者的引用.仅创建从发布者到订阅者的引用(通过EventHandler)......在这种情况下,当GC在订阅者之前收集发布者(订阅者生存期大于发布者)时,没有问题.

但是...据我所知,GC 何时收集发布者并不能保证,所以理论上,即使订阅者的生命周期长于发布者,订阅者也可能会合法收集,但是发布者仍然没有被收集(我不知道在最近的 GC 周期内,GC 是否会足够聪明地先收集发布者,然后再收集订阅者.

无论如何,在这种情况下,由于我的订阅者没有直接引用发布者并且无法取消订阅事件,我想让发布者实现 IDisposable,以便在删除对他的所有引用之前将其处理掉(参见在我的示例中的 CustomNotificationSystemHandler 中).

再一次 我应该在发布者处理方法中写什么以清除对订阅者的所有引用?应该是 EventHappened -= null;或 EventHappened = null;或者没有办法以这种方式做到这一点,我需要做类似下面的东西???

公共事件 EventHandler EventHappened{添加{eventTable["EventHappened"] = (EventHandler)eventTable["EventHappened"] + value;}消除{eventTable["EventHappened"] = (EventHandler)eventTable["EventHappened"] - 值;}}

解决方案

我已经在你的示例代码中添加了我的评论.

A 类:IDisposable{公共事件 EventHandler EventHappened{添加{eventTable["EventHappened"] = (EventHandler)eventTable["EventHappened"] + value;}消除{eventTable["EventHappened"] = (EventHandler)eventTable["EventHappened"] - 值;}}公共无效处置(){//Amit: 如果你只有一个事件'EventHappened',//你可以清除订阅者如下eventTable["EventHappened"] = null;//Amit: EventHappened = null 在这里不起作用//只是一个语法糖来清除编译器生成的支持委托.//由于您添加了添加"和删除",因此没有生成编译器//委托清除////以上只是为了解释这个概念.//如果eventTable是EventHandlers的字典//你可以简单地调用'clear'.//即使有更多像EventHappened这样的事件,这也会起作用}}B类{公共 B(A a){a.EventHappened += new EventHandler(this.HandleEventB);//你绝对在这里.//B类不存储对A的任何引用//订阅事件不会添加任何对发布者的引用//这里你所做的只是调用'EventHappened'的'Add'方法//向它传递一个持有对B的引用的委托.//因此有一条从A到B的路径,但不是反向的.}公共无效HandleEventB(对象发送者,EventArgs args){}}C类{公共 C(A a){a.EventHappened += new EventHandler(this.HandleEventC);}公共无效HandleEventC(对象发送者,EventArgs args){}}类服务{一个_a;无效开始(){CustomNotificationSystem.OnEventRaised += new EventHandler(CustomNotificationSystemHandler)_a = 新的 A();//Amit:你是对的,所有这些都不存储对_a的任何引用B b1 = 新 B(_a);B b2 = 新 B(_a);C c1 = 新 C(_a);C c2 = 新 C(_a);}无效 CustomNotificationSystemHandler(args){//Amit: 你决定 _a 已经过了它的生命并且必须被处理掉.//这里我假设你想处理它以停止触发它的事件//稍后会详细介绍_a.Dispose();//Amit: 现在 _a 指向一个全新的 A 和之前的实例//有资格收集,因为没有活动引用//前一个 _a 现在_a = 新的 A();}}

<块引用>

b1,b2,c1,c2 将继续存在,我知道他们现在会订阅了 _a 的前一个实例,我没关系,但在那例如,现在,没有人引用 _a 的前一个实例(b 不是持有对 _a) 的引用,根据我的理论,之前的 _a 实例,现在可能会被收集...或者我错过了什么???

正如我在上面代码中的评论所解释的,你在这里没有遗漏任何东西:)

<块引用>

但是...据我所知,不能保证 GC 何时会收集发布者在理论上如此,即使订阅者的生命周期更长出版商,订阅者可能是合法的收集,但是发布者仍未收集(我不知道是否在最近的 GC 内循环,GC 将足够聪明地先收集发布者,然后再收集订阅者.

由于发布者引用订阅者,因此订阅者在发布者之前获得收集资格的情况永远不会发生,但反过来可能是正确的.如果发布者在订阅者之前被收集,那么正如您所说,没有问题.如果订阅者属于比发布者更低的 GC 代,那么由于发布者持有对订阅者的引用,GC 会将订阅者视为可访问的并且不会收集它.如果两者都属于同一代,则将它们一起收集.

<块引用>

因为我的订阅者没有直接引用发布者和无法取消订阅活动,我想让发布者实现 IDisposable

与某些人的建议相反,如果您在任何时候确定不再需要该对象,我建议您实施 dispose.简单地更新对象引用可能并不总是导致对象停止发布事件.

考虑以下代码:

类 MainClass{公共静态发布者发布者;静态无效主要(){发布者 = 新发布者();线程 eventThread = new Thread(DoWork);eventThread.Start();Publisher.StartPublishing();//继续触发事件}静态无效 DoWork(){var 订阅者 = 新订阅者();订户=空;//Subscriber 仅被发布者的 SomeEvent 引用线程.睡眠(200);//我们已经等够了,我们现在不需要Publisher发布者 = null;GC.Collect();//即使在GC.Collect之后,即使我们将Publisher设置为null,也不会收集发布者//这是因为此时'StartPublishing'方法正在执行中//这意味着它可以从主线程的堆栈中隐式访问(通过'this'指针)//这也意味着订阅者仍然活着//即使我们打算让发布者停止发布,由于主线程对它的某种隐藏"引用,它仍会继续触发事件!!!!}}内部类发布者{公共无效开始发布(){线程.睡眠(100);InvokeSomeEvent(null);线程.睡眠(100);InvokeSomeEvent(null);线程.睡眠(100);InvokeSomeEvent(null);线程.睡眠(100);InvokeSomeEvent(null);}公共事件 EventHandler SomeEvent;公共无效InvokeSomeEvent(对象e){EventHandler 处理程序 = SomeEvent;如果(处理程序!= null){处理程序(此,空);}}〜发布者(){Console.WriteLine("我从不打印");}}内部类订阅者{公共订阅者(){如果(MainClass.Publisher != null){MainClass.Publisher.SomeEvent += PublisherSomeEvent;}}void PublisherSomeEvent(对象发送者,EventArgs e){如果(MainClass.Publisher == null){//null怎么触发事件!!!引发异常抛出新异常(Boooooooooommmm");//但是注意'sender'不为空}}}

如果您运行上述代码,您通常会收到Boooooooooommmm".因此,我们的想法是事件发布者必须在我们确定其生命周期结束时停止触发事件.

这可以通过 Dispose 方法完成.

有两种方法可以实现:

  1. 设置标志IsDisposed"并在触发任何事件之前对其进行检查.
  2. 清除事件订阅者列表(如我在代码中的评论中所建议的那样).

2 的好处是您释放对订阅者的任何引用,从而启用那里的收集(正如我之前解释的,即使发布者是垃圾但属于更高代,那么它仍可能会延长低代订阅者的收集).

虽然,诚然,由于发布者的隐藏"可访问性,您很少会遇到所展示的行为,但正如您所见,2 的好处是显而易见的,并且对所有事件发布者都有效,尤其是长期存在的发布者(Singleton任何人!!!).这本身就值得实现 Dispose 并使用 2.

EDIT: After Joel Coehoorns excellent answer, I understand that I need to be more specific, so I modified my code to be closer to thing I'm trying to understand...

Events: As I understand, in the background the events are "collection" of EventHandlers aka Delegates which will be executed when event raised. So for me it means that if object Y has event E and object X subscribes to event Y.E, then Y will have reference to X, since Y must execute the method located in X, in that way, X can not be collected, and that thing i understand.

//Creates reference to this (b) in a.
a.EventHappened += new EventHandler(this.HandleEvent);

But it is not what Joel Coehoorn tells...

However, there is an issue with events such that sometimes people like to use IDisposable with types that have events. The problem is that when a type X subscribes to events in another type Y, X now has a reference to Y. This reference will prevent Y from being collected.

I not understand how X will reference the Y ???

I modified a bit my example to illustrate my case more closer:

class Service //Let's say it's windows service that must be 24/7 online
{       
    A _a;

    void Start()
    {
       CustomNotificationSystem.OnEventRaised += new EventHandler(CustomNotificationSystemHandler)
       _a = new A();

       B b1 = new B(_a);
       B b2 = new B(_a);
       C c1 = new C(_a);
       C c2 = new C(_a);
    }

    void CustomNotificationSystemHandler(args)
    {

        //_a.Dispose(); ADDED BY **EDIT 2***
        a.Dispose();

        _a = new A();
        /*
        b1,b2,c1,c2 will continue to exists as is, and I know they will now subscribed
        to previous instance of _a, and it's OK by me, BUT in that example, now, nobody
        references the previous instance of _a (b not holds reference to _a) and by my
        theory, previous instance of _a, now may be collected...or I'm missing
        something???
        */
    }

}  

class A : IDisposable
        {
           public event EventHandler EventHappened;
        }

        class B
        {          
           public B(A a) //Class B does not stores reference to a internally.
           {
              a.EventHappened += new EventHandler(this.HandleEventB);
           }

           public void HandleEventB(object sender, EventArgs args)
           {
           }
        }

        class C
        {          
           public C(A a) //Class B not stores reference to a internally.
           {
              a.EventHappened += new EventHandler(this.HandleEventC);
           }

           public void HandleEventC(object sender, EventArgs args)
           {
           }
        }

EDIT 2: OK, now it's clear, when subscriber subscribes to a publishers events, it's NOT creates a reference to the publisher in subscriber. Only the reference from publisher to subscriber created (through EventHandler)...in this case when publisher collected by GC before the subscriber (subscribers lifetime is greater then publishers), there's no problem.

BUT...as I know, it's not guaranteed when GC will collect the publisher so in theory, even if subscribers lifetime is greater then publishers, it can happen that subscriber is legal for collection, but publisher is still not collected (I don't know if within closest GC cycle, GC will be smart enough to collect publisher first and then subscriber.

Anyway, in such case, since my subscriber do not have direct reference to publisher and can't unsubscribe the event, I would like to make publisher to implement IDisposable, in order to dispose it before delete all references to him (see in CustomNotificationSystemHandler in my example).

AND AGAIN What I should write in publishers dispose method in order to clear all references to subscribers? should it be EventHappened -= null; or EventHappened = null; or there's no way to do it in such way, and I need to make something like below ???

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

解决方案

I have added My comments in your sample code.

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

   public void Dispose()
   {
      //Amit: If you have only one event 'EventHappened', 
      //you can clear up the subscribers as follows

      eventTable["EventHappened"] = null;

      //Amit: EventHappened = null will not work here as it is 
      //just a syntactical sugar to clear the compiler generated backing delegate.
      //Since you have added 'add' and 'remove' there is no compiler generated 
      //delegate to clear
      //
      //Above was just to explain the concept.
      //If eventTable is a dictionary of EventHandlers
      //You can simply call 'clear' on it.
      //This will work even if there are more events like EventHappened          
   }
}

class B
{          
   public B(A a)
   {
      a.EventHappened += new EventHandler(this.HandleEventB);

      //You are absolutely right here.
      //class B does not store any reference to A
      //Subscribing an event does not add any reference to publisher
      //Here all you are doing is calling 'Add' method of 'EventHappened'
      //passing it a delegate which holds a reference to B.
      //Hence there is a path from A to B but not reverse.
   }

   public void HandleEventB(object sender, EventArgs args)
   {
   }
}

class C
{          
   public C(A a)
   {
      a.EventHappened += new EventHandler(this.HandleEventC);
   }

   public void HandleEventC(object sender, EventArgs args)
   {
   }
}

class Service
{       
    A _a;

    void Start()
    {
       CustomNotificationSystem.OnEventRaised += new EventHandler(CustomNotificationSystemHandler)

       _a = new A();

       //Amit:You are right all these do not store any reference to _a
       B b1 = new B(_a);
       B b2 = new B(_a);
       C c1 = new C(_a);
       C c2 = new C(_a);
    }

    void CustomNotificationSystemHandler(args)
    {

        //Amit: You decide that _a has lived its life and must be disposed.
        //Here I assume you want to dispose so that it stops firing its events
        //More on this later
        _a.Dispose();

        //Amit: Now _a points to a brand new A and hence previous instance 
        //is eligible for collection since there are no active references to 
        //previous _a now
        _a = new A();
    }    
}

b1,b2,c1,c2 will continue to exists as is, and I know they will now subscribed to previous instance of _a, and it's OK by me, BUT in that example, now, nobody references the previous instance of _a (b not holds reference to _a) and by my theory, previous instance of _a, now may be collected...or I'm missing something???

As explained through my comments in the above code, you are not missing anything here :)

BUT...as I know, it's not guaranteed when GC will collect the publisher so in theory, even if subscribers lifetime is greater then publishers, it can happen that subscriber is legal for collection, but publisher is still not collected (I don't know if within closest GC cycle, GC will be smart enough to collect publisher first and then subscriber.

Since publisher references subscriber, it can never happen that the subscriber becomes eligible for collection before the publisher but reverse can be true. If publisher gets collected before subscriber then, as you said, there is no problem. If the subscriber belongs to a lower GC generation than publisher then since publisher holds a reference to subscriber, GC will treat the subscriber as reachable and will not collect it. If both belong to same generation, they will be collected together.

since my subscriber do not have direct reference to publisher and can't unsubscribe the event, I would like to make publisher to implement IDisposable

Contrary to what some have suggested, I would recommend implementing dispose if at any point you are deterministically sure that the object is no longer required. Simply updating an object reference may not always lead to an object stop publishing events.

Consider the following 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
        }
    }
}

If you run the above code, more often than not you will receive the 'Booooooooommmm'. Hence idea is that event publisher must stop firing events when we are sure that its life is up.

This can be done through Dispose method.

There are two ways to achieve this:

  1. Set a flag 'IsDisposed' and check it before firing any event.
  2. Clear up the event subscribers list (as suggested in my comments in your code).

Benefit of 2 is that you release any reference to the subscribers, thereby enabling there collection (as I explained earlier even if the publisher is garbage but belongs to higher generation then it may still prolong collection of lower generation subscribers).

Though, admittedly, it will be quite rare that you experience the demonstrated behavior due to 'hidden' reachability of the publisher but as you can see benefits of 2 are clear and are valid for all event publishers especially long living ones (Singletons anybody!!!). This itself makes it worth to implement Dispose and go with 2.

这篇关于.NET 对象事件和处置/GC的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆