.NET对象的事件和处置/ GC(.NET object events and dispose /

2019-06-26 07:22发布

编辑:乔尔Coehoorns优秀的答案后,我明白我需要更具体,所以我修改我的代码更接近我想明白的事...

事件:据我了解,在后台的事件是事件处理又名代表时引发的事件将被执行的“收藏”。 所以对我来说这意味着如果对象Y具有事件E和对象X订阅事件YE,Y将参照X,因为Y应当执行位于X的方法,以这种方式,X不能被收集 ,并事我明白了。

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

但它不是什么乔尔Coehoorn告诉...

然而,与事件的问题,例如,有时人们喜欢使用IDisposable接口与具有事件类型。 问题是,当X型订阅到另一个Y型事件,X现在有一个参考Y.该参考将阻止值Y被收集。

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

我修改了一下我的例子来说明我的情况更接近:

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)
           {
           }
        }

编辑2:好了,现在很明显,当用户签约的一个出版商的事件,它不是建立在用户发布的参考。 只有从出版商的参考用户创建(通过事件处理程序)......在这种情况下,当收集的出版商通过GC之前的用户(用户寿命大于出版社),没有任何问题。

但是 ...我知道,它不能保证时,GC将收集出版商因此从理论上讲,即使用户寿命大于出版商,可能发生的是用户是合法的集合,但出版商仍然没有收集(我不知道如果最接近的GC周期内,GC将是足够聪明,先收集用户发行。

不管怎样,在这种情况下,由于我的用户不必直接引用出版商和无法取消的情况下,我想使出版商实现IDisposable,为了处理之前删除所有对他(见CustomNotificationSystemHandler在我的例子)。

又一次 ,我应该在出版商写以清除给用户的所有引用处理方法? 它应该被EventHappened - = NULL; 或EventHappened = NULL; 或者有没有办法做到这一点在这样的方式,我需要做类似下面???

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

Answer 1:

我已经在你的代码示例添加我的评论。

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将继续按原样存在,我知道他们现在订阅_a的前一个实例,它由我没关系,但在这个例子中,到目前为止,还没有人引用_a的前一个实例(B不持有参考_a)和我的理论,以前的实例_a的,现在可以收集......或者我失去了一些东西???

通过我的意见在上面的代码解释,你是不是在这里缺少什么:)

但是...我知道,它不能保证时,GC将收集出版商因此从理论上讲,即使用户寿命大于出版商,可能发生的是用户是合法的集合,但出版商仍然没有收集(我不知道如果最接近的GC周期内,GC将是足够聪明,先收集用户发行。

由于出版商引用用户,它永远不可能发生的订户成为出版商之前符合回收,但是反向可能是真的。 如果发行商被用户之前收集的话,就像你说的,是没有问题的。 如果用户属于低GC一代比出版商则由于发布者保持对用户的参考,GC会将用户的访问,并不会收集它。 如果两者都属于同一代,他们将被收集在一起。

因为我的用户不必直接引用出版商和无法取消的情况下,我想使出版商实现IDisposable

相反,有些建议,我会建议实施处置,如果在任何时候,你是确定性确信对象不再需要。 简单地更新一个对象的引用可能并不总是导致对象停止发布事件。

考虑下面的代码:

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
        }
    }
}

如果你运行上面的代码,往往不是你会收到“Booooooooommmm”。 因此,想法是,事件发布者必须停止射击的事件时,我们相信,它的寿命可达。

这可以通过Dispose方法来完成。

有两种方法来实现:

  1. 设置标记“IsDisposed”和射击任何事件之前检查它。
  2. 清理事件订阅列表(在你的代码在我的意见建议)。

2好处是,你发布的任何参考用户,从而使那里收集(正如我刚才解释,即使出版商是垃圾,但属于更高世代那么它可能仍然延长低一代的用户的集合)。

虽然,不可否认,这将是相当罕见的,你遇到的证明行为由于出版商的“隐藏的”可达性,但正如你所看到的2好处是显而易见的,并适用于所有事件发布者特别是长期居住的人(单身人! !)。 这本身就是值得实现Dispose和2个去。



Answer 2:

对象B的寿命时间是长于A,所以A可以较早设置

这听起来像你混淆了“处置”和“收藏”? 处置的对象无关 ,与内存或垃圾收集。 为了确保一切都清楚了,让我们分手的两种情况,然后我就上移至末事件:

采集:

不管你做什么都不会允许一个要收集它的父B.只要B是到达之前,所以是A.即使是私有的,它仍然是可到达的任何代码B内部,所以只要B是可达的, A被认为是可到达。 这意味着,垃圾回收器不会知道肯定,你用它做,绝不会收集,直到它也是安全的收集B.即使你明确地调用GC.Collect()或类似这是真实的。 只要对象是可达的,它不会被收集。

处理:

我甚至不知道为什么你在这里(它无关 ,与内存或垃圾收集)实现IDisposable,但我给你的疑问的,我们只是没有看到非托管资源的那一刻的利益。

没有什么能阻止你从上设置,只要你想。 只需拨打a.Dispose(),它完成。 只有这样,.net框架将永远调用Dispose()为您自动在结束using块。 的Dispose()垃圾回收时,不会调用,除非你做它(在某一时刻更多的终结)对象的终结器的一部分。

当实现了IDisposable,你要发送消息给程序员这种类型应该(甚至是“必须”)及时处置。 存在用于任何IDisposable的对象(在图中的两个变型)两个正确的图案。 所述第一图案是包围类型本身在使用块。 当这是不可能的(例如:代码如你其中类型是另一种类型的一个成员),所述第二模式是父类型也应实现IDisposable因此它可以本身然后被包括在使用块,和它的Dispose()方法可以调用你的类型的Dispose()。 对这些模式的变化是使用try / finally块来代替使用块,在那里你调用Dispose()在finally块的。

现在到终结。 您需要实现一个终结的唯一时间是在发起一个非托管资源的IDisposable的类型。 因此,举例来说,如果你的A类以上仅仅是包装像SqlConnection的一类,它并不需要一个终结,因为在SqlConnection的本身的终结将采取任何必要的清理照顾。 但是,如果你的A型正在实施到了一个新的数据库引擎的连接,你想一个终结,以确保在回收对象时,你的连接将被关闭。 您的B型,然而,就不需要一个终结,尽管它管理/包装你的类型A,因为A型将敲定连接的照顾。

事件:

从技术上讲,事件仍在托管代码,不应该需要处理。 然而,与事件的问题,例如,有时人们喜欢使用IDisposable接口与具有事件类型。 问题是,当X型订阅到另一个Y型事件,Y现在有一个参考X.该参考,可以防止X被收集。 如果您希望y以具有更长的寿命,那么X,你可以在这里遇到的问题,特别是如果Y是很长的寿命相对于许多X的来来去去随着时间的推移。

为了解决这个问题,有时程序员都会有Y型实现IDisposable和Dispose()方法的目的是取消,这样订阅对象也可以收集任何事件。 从技术上讲,这不是的Dispose()模式的目的,但它的作品不够好,我不打算争辩一下。 有两件事情你需要使用这种模式的事件时,需要了解:

  1. 你并不需要一个终结,如果这是实现IDisposable接口的唯一原因
  2. 你的类型的实例仍然需要使用或尝试/ finally块,或者你还没有获得任何东西。 否则的Dispose()将不会被调用,你的对象仍然无法收集。

在这种情况下,你的A型是专用于B型,所以只有B型可以订阅的事件。 由于“a”是B型的成员,也不是符合垃圾回收,直到B是不再可达,在该点既将不再是可到达的且事件订阅参考不能计数。 这意味着由A的事件在B持有不会阻止B,从收集的参考。 但是,如果你在其他地方使用的A型,你可能仍然希望有一个实现IDisposable,以确保您的活动都取消订阅。 如果你这样做,一定要跟随整个模式,使得A的情况下,被封闭在使用或尝试/ finally块。



Answer 3:

相反的是一些其他的答案宣称, 其事件发布者的GC寿命可能超过订户的使用寿命应视为非托管资源 。 该术语在短语“非托管”,“非托管资源”并不意味着“完全的托管代码外面的世界”,而是涉及到对象是否需要清理超过由管理垃圾收集器提供。

例如,一个集合,可能会令CollectionChanged事件。 如果重复创建和废弃该订阅这样的事件一些其它类型的目的,所述收集可能最终保持一个委托参照每个这样的对象。 如果这样的创作和遗弃发生例如每秒一次(如果有问题的对象,更新UI窗口例行创建为可能发生的),这样的引用的数量可以用86,000多名的每一天成长,该程序运行。 不是一个大问题这是从来没有超过几分钟运行一个程序,但可能几个星期在同一时间运行的程序的绝对杀手。

这真是不幸的是,微软并没有拿出在vb.net或C#更好的事件的清理模式。 有很少任何理由为什么它放弃前订用事件类实例不能清除它们,但微软没有采取任何行动来促进这种清理。 在实践中,可以逃脱放弃那个必要努力,以确保事件烦人的级别正确清理,没有按订阅了事件足够频繁(因为事件发布者将走出去的范围大约在同一时间,因为用户)对象“T似乎值得的。 不幸的是,它并不总是容易预测到所有的地方事件发布可能活得比预期的情况; 如果很多类离开事件晃来晃去,有可能为大量内存是无法收回的,因为事件订阅一个恰好属于一个长期目标。

附录响应编辑

如果X是订阅从事件Y ,然后放弃所有引用Y ,如果Y收集有资格, X不会阻止Y被收集。 这将是一件好事。 如果X是保持强引用Y为能够处理它的目的,这样的引用将防止Y被收集。 这也许可以说没有这样的好事。 在某些情况下,这将是更好的为X ,以保持长期WeakReference (一个设为第二个参数构建true ),以Y而不是直接引用; 如果目标WeakReference是非空当XDispose d,就必须取消订阅Y的事件。 如果目标为空,也不能退订,但都不会有问题,因为那时Y (其参考X )将已经完全不复存在。 注意,在万一Y死亡和复活, X还是会想要取消它的事件; 使用长WeakReference将确保仍然可能发生。

虽然有些人会认为, X不应该去保持一个参考Y ,并Y应该简单地被写入使用某种弱事件分派的,这种行为是不是在一般情况下,正确的,因为没有办法为Y告诉是否X会做任何其他代码可能会在意 。 这是完全可能的, X可能持有一些强烈的层次对象的引用,并且可能做一些事来的事件处理程序中的其他对象。 这一事实Y拥有的唯一参考X不应意味着没有其他对象是“兴趣” X 。 唯一一般,正确的办法是有其不再感兴趣的其他对象的事件事实通知后者对象的对象。



Answer 4:

我有我的B类实现IDisposable,以及和它的处理程序,我会首先检查是否不为空,然后处理A.通过使用这种方法,你必须公正,确保处置类的最后和内部将处理所有其他配置。



Answer 5:

你并不需要解开一个对象的处置时的事件处理程序,虽然你可能 。 我的意思是,GC将清理事件处理就好了,而不需要您进行任何干预,但是根据情况,您可能希望GC确实为了防止处理程序之前,当您间没有”被称为消除这些事件处理程序Ť期待它。

在你的榜样,我认为你有你的角色颠倒-类A真的不应该是别人加退订的事件处理程序,并没有真正的需要删除的事件处理程序eiether,因为它可以而不只是停止提高这些事件!

但假设情况相反

class A
{
   public EventHandler EventHappened;
}

class B : IDisposable
{
    A _a;
    private bool disposed;

    public B(A a)
    {
        _a = a;
        a.EventHappened += this.HandleEvent;
    }

    public void Dispose(bool disposing)
    {
        // As an aside - if disposing is false then we are being called during 
        // finalization and so cannot safely reference _a as it may have already 
        // been GCd
        // In this situation we dont to remove the handler anyway as its about
        // to be cleaned up by the GC anyway
        if (disposing)
        {
            // You may wish to unsubscribe from events here
            _a.EventHappened -= this.HandleEvent;
            disposed = true;
        }
    }

    public void HandleEvent(object sender, EventArgs args)
    {
        if (disposed)
        {
            throw new ObjectDisposedException();
        }
    }
 }

如果可能的A继续提高事件即使B已被释放,并为事件处理程序B是否可以做一些事情,可能会引起其他异常或其他一些意外行为B布置那么它可能是一个好主意,从这个退订事件第一。



Answer 6:

MSDN参考

“要防止事件处理程序被调用时引发该事件,从事件退订。为了防止资源泄漏,您应该从事件退订您处理一个用户对象之前,直到你从一个事件,多播委托退订这underlies在发布对象中的事件有一个封装了用户的事件处理程序委托的引用。只要发布对象认为引用,垃圾收集不会删除你的用户对象“。

“当所有的用户都从事件退订,在发布类的事件实例设置为null。”



Answer 7:

通过EventHandler委托对象A的引用B(A具有事件处理器至极引用B的一个实例)。 当A设置为NULL,将收集和内存将被释放B并未有任何引用A.。 所以,你不需要在这种情况下,以清除任何东西。



文章来源: .NET object events and dispose / GC