C# .NET对象事件和dispose/GC
编辑:在Joel Coehoorns出色的回答之后,我明白我需要更具体一些,所以我修改了我的代码以便更接近我想要理解的东西 事件:据我所知,在后台,事件是事件处理程序(又称委托)的“集合”,在事件引发时将执行这些事件。所以对我来说,这意味着如果对象Y有事件E,而对象X订阅了事件Y.E,那么Y将引用X,因为Y必须执行位于X中的方法,这样,X就不能被收集到,我明白这一点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
//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
}
}
}