Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/25.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# .NET对象事件和dispose/GC_C#_.net_Events_Garbage Collection_Dispose - Fatal编程技术网

C# .NET对象事件和dispose/GC

C# .NET对象事件和dispose/GC,c#,.net,events,garbage-collection,dispose,C#,.net,Events,Garbage Collection,Dispose,编辑:在Joel Coehoorns出色的回答之后,我明白我需要更具体一些,所以我修改了我的代码以便更接近我想要理解的东西 事件:据我所知,在后台,事件是事件处理程序(又称委托)的“集合”,在事件引发时将执行这些事件。所以对我来说,这意味着如果对象Y有事件E,而对象X订阅了事件Y.E,那么Y将引用X,因为Y必须执行位于X中的方法,这样,X就不能被收集到,我明白这一点 //Creates reference to this (b) in a. a.EventHappened += new Even

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

事件:据我所知,在后台,事件是事件处理程序(又称委托)的“集合”,在事件引发时将执行这些事件。所以对我来说,这意味着如果对象Y有事件E,而对象X订阅了事件Y.E,那么Y将引用X,因为Y必须执行位于X中的方法,这样,X就不能被收集到,我明白这一点

//Creates reference to this (b) in a.
a.EventHappened += new EventHandler(this.HandleEvent);
但这不是乔尔·科霍恩所说的……

但是,事件存在一个问题,有时人们喜欢将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:好的,现在很清楚,当订阅者订阅发布者事件时,不会在订阅者中创建对发布者的引用。仅创建了从发布服务器到订阅服务器的引用(通过EventHandler)…在这种情况下,当GC在订阅服务器之前收集发布服务器时(订阅服务器的生存期大于发布服务器),没有问题

但是…正如我所知,GC收集发布者的时间并不保证,因此从理论上讲,即使订阅者的生存期大于发布者,也可能发生订阅者合法收集的情况,但发布者仍然未被收集(我不知道在最近的GC周期内,GC是否足够聪明,可以先收集发布者,然后再收集订阅者

无论如何,在这种情况下,由于我的订阅者没有对publisher的直接引用,并且无法取消订阅事件,因此我希望让publisher实现IDisposable,以便在删除对他的所有引用之前对其进行处理(请参见我的示例中的CustomNotificationSystemHandler)

再次为了清除对订阅者的所有引用,我应该在publishers dispose方法中写些什么?应该是eventOccurrend-=null;或者eventOccurrend=null;或者没有办法这样做,我需要做如下操作

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

我会让我的类B实现IDisposable,在它的dispose例程中,我会首先检查A是否为null,然后再处理A。通过使用这种方法,您只需确保处理类的最后一个,内部将处理所有其他dispose

对象B的寿命比A长,因此A可以被更早地处理

听起来你把“处理”和“收集”混淆了?处理对象与内存或垃圾收集没有任何关系。为了确保一切都清楚,让我们将这两种情况分开,然后我将继续讨论最后的事件:

收藏:

您所做的一切都不会允许A在其父级B之前被收集。只要B是可访问的,A也是可访问的。即使A是私有的,它仍然可以从B中的任何代码访问,只要B是可访问的,A就被认为是可访问的。这意味着垃圾收集器不确定您是否已经完成了它,并且会除非收集B也是安全的,否则永远不要收集A。即使显式调用GC.collect()或类似函数,也是如此。只要对象是可访问的,就不会收集它

处置:

我甚至不知道您为什么要在这里实现IDisposable(它与内存或垃圾收集无关),但我会让您暂时放心,因为我们看不到非托管资源

没有任何东西可以阻止您随时处理。只要调用A.Dispose(),就可以了。.Net framework为您自动调用Dispose()的唯一方法是在
使用
block的末尾。在垃圾收集期间不会调用Dispose(),除非您将其作为对象终结器的一部分执行(稍后将详细介绍终结器)

在实现IDisposable时,您正在向程序员发送一条消息,说明应该(甚至可能“必须”)立即处理此类型。对于任何IDisposable对象,有两种正确的模式(模式上有两种变体)。第一种模式是将类型本身包含在using块中。如果不可能的话(例如:像您这样的代码,其中类型是另一个类型的成员),第二种模式是父类型也应该实现IDisposable,这样它自己就可以包含在using块中,并且它的Dispose()可以调用您类型的Dispose()。这些模式的变体是使用try/finally块,而不是在finally块中调用Dispose()的using块

现在转到终结器。您唯一需要实现终结器的时间是针对发起非托管资源的IDisposable类型。因此,例如,如果上面的类型a只是包装一个类,如SqlConnection,则它不需要终结器,因为SqlConnection中的终结器本身将负责任何需要的清理。但是,如果如果类型A正在实现与一种全新数据库引擎的连接,则您需要一个终结器,以确保在收集对象时关闭连接。但是,您的类型B不需要终结器,即使它管理/包装您的类型A,因为类型A将负责终结连接

事件:

从技术上讲,事件仍然是托管代码,不需要处理。但是,事件存在一个问题,有时人们喜欢将IDisposable与具有
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();
        }
    }
 }
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();
    }    
}
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
        }
    }
}