Ale*_* Dn 9 .net c# events garbage-collection dispose
编辑:在Joel Coehoorns的优秀答案之后,我明白我需要更加具体,所以我修改了我的代码,使其更贴近我正在努力理解的事情......
事件:据我所知,在后台,事件是EventHandlers又名代表的"集合",将在事件引发时执行.所以对我来说,这意味着如果对象Y有事件E而对象X订阅事件YE,那么Y将引用X,因为Y必须执行位于X中的方法,这样就不能收集X,并且我理解的事情.
//Creates reference to this (b) in a.
a.EventHappened += new EventHandler(this.HandleEvent);
Run Code Online (Sandbox Code Playgroud)
但这不是Joel 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)
{
}
}
Run Code Online (Sandbox Code Playgroud)
编辑2:好的,现在很清楚,当订阅者订阅发布者事件时,它不会在订阅者中创建对发布者的引用.只有从发布者到订阅者的引用创建(通过EventHandler)...在这种情况下,当发布者在订阅者之前收集发布者(订阅者的生命周期大于发布者)时,没有问题.
但是 ......据我所知,当GC收集出版商时,不能保证理论上,即使订阅者的生命周期比出版商更大,也可能发生订阅者合法收集,但发行人仍未收集(我不知道)要知道,如果在最接近的GC周期内,GC将足够聪明,首先收集发布者,然后收集订阅者.
无论如何,在这种情况下,由于我的订阅者没有直接引用发布者并且无法取消订阅该事件,我想让发布者实现IDisposable,以便在删除所有对他的引用之前将其处理掉(参见CustomNotificationSystemHandler in我的例子).
再次,我应该在发布商处理方法中写出什么来清除所有对订阅者的引用?应该是EventHappened - = null; 或EventHappened = null; 或者没有办法以这种方式做到这一点,我需要做下面的事情???
public event EventHandler EventHappened
{
add
{
eventTable["EventHappened"] = (EventHandler)eventTable["EventHappened"] + value;
}
remove
{
eventTable["EventHappened"] = (EventHandler)eventTable["EventHappened"] - value;
}
}
Run Code Online (Sandbox Code Playgroud)
Joe*_*orn 13
物体B的寿命比A长,因此A可以更早地处理
听起来你在混淆"处置"和"收藏"?处理对象与内存或垃圾回收无关.为了确保一切都清楚,让我们分解两个场景,然后我将继续讨论最后的事件:
采集:
没有什么你做永远不会允许一个要收集它的父B.只要B是之前到达,所以是A.即使是私有的,它仍然是可到达的任何代码B内部,所以只要B是可达的, A被认为是可达的.这意味着垃圾收集器不确定您是否完成了它,并且在收集B之前永远不会收集A.即使您明确调用GC.Collect()或类似的东西也是如此.只要对象可以访问,就不会收集它.
处理:
我甚至不确定你为什么要在这里实现IDisposable(它与内存或垃圾收集无关),但是我会给你带来怀疑的好处,因为我们只是看不到非托管资源.
没有什么能阻止您随时处置A.只需调用a.Dispose(),就完成了..Net框架将自动为您调用Dispose()的唯一方法是在using
块结束时.在垃圾收集期间不会调用Dispose(),除非您将其作为对象的终结器的一部分(稍后更多关于终结器).
在实现IDisposable时,您向程序员发送一条消息,即此类型应该(甚至可能"必须")立即处理.任何IDisposable对象都有两种正确的模式(模式有两种变体).第一种模式是将类型本身封装在一个使用块中.当这是不可能的时(例如:类型是其他类型的成员的代码),第二种模式是父类型也应该实现IDisposable,因此它本身可以包含在一个使用块中,它是Dispose()可以调用你的类型的Dispose().这些模式的变化是使用try/finally块而不是using块,在finally块中调用Dispose().
现在到终结者.您需要实现终结器的唯一时间是用于发起非托管资源的IDisposable类型.因此,例如,如果上面的类型A只是包装类似SqlConnection的类,则它不需要终结器,因为SqlConnection本身的终结器将负责任何所需的清理.但是,如果您的类型A实现了与全新数据库引擎的连接,那么您需要一个终结器来确保在收集对象时关闭连接.但是,类型B不需要终结器,即使它管理/包装您的类型A,因为类型A将负责完成连接.
事件:
从技术上讲,事件仍然是托管代码,不需要处理.但是,事件存在问题,有时人们喜欢将IDisposable与具有事件的类型一起使用.问题是,当类型X订阅另一个类型Y的事件时,Y现在具有对X的引用.该引用可以防止收集X. 如果你期望Y的寿命比X长,那么你可能会遇到问题,特别是如果Y相对于随时间变化的许多X来说是非常长寿的.
为了解决这个问题,有时程序员会使用Y类实现IDisposable,而Dispose()方法的目的是取消订阅任何事件,以便也可以收集订阅对象.从技术上讲,这不是Dispose()模式的目的,但它运作良好,我不会争论它.将此模式与事件一起使用时,您需要了解两件事:
在这种情况下,类型A对于类型B是私有的,因此只有类型B可以订阅A的事件.由于'a'是B类的成员,因此在B不再可达之前,它们都不符合垃圾收集条件,此时两者将不再可访问,并且事件订阅引用将不计算.这意味着A事件在B上持有的引用不会阻止B被收集.但是,如果您在其他地方使用A类型,您可能仍希望具有A实现IDisposable以确保您的事件已取消订阅.如果这样做,请确保遵循整个模式,以便A的实例包含在using或try/finally块中.
我在您的示例代码中添加了我的评论。
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();
}
}
Run Code Online (Sandbox Code Playgroud)
b1,b2,c1,c2 将继续按原样存在,我知道他们现在将订阅 _a 的前一个实例,我没问题,但是在那个例子中,现在,没有人引用 _a 的前一个实例(b 不是持有对 _a) 的引用,根据我的理论,以前的 _a 实例现在可能会被收集……或者我遗漏了什么???
正如我在上面代码中的评论所解释的那样,您在这里没有遗漏任何东西:)
但是……据我所知,不能保证 GC 何时收集发布者,所以理论上,即使订阅者的生命周期比发布者长,也可能发生订阅者对收集是合法的,但发布者仍然没有被收集(我不不知道是否在最近的 GC 周期内,GC 将足够聪明,可以先收集发布者,然后再收集订阅者。
由于发布者引用了订阅者,因此订阅者在发布者之前获得收集资格的情况永远不会发生,但反向可能为真。如果发布者在订阅者之前被收集,那么正如您所说,没有问题。如果订阅者属于比发布者更低的 GC 代,那么由于发布者持有对订阅者的引用,GC 会将订阅者视为可访问的并且不会收集它。如果两者属于同一代,则将它们收集在一起。
由于我的订阅者没有直接引用发布者并且无法取消订阅事件,我想让发布者实现 IDisposable
与某些人的建议相反,如果您在任何时候确定不再需要该对象,我建议实施 dispose。简单地更新一个对象引用可能并不总是导致一个对象停止发布事件。
考虑以下代码:
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
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果您运行上面的代码,您通常会收到“Booooooooommmm”。因此,想法是事件发布者必须在我们确定其生命周期结束时停止触发事件。
这可以通过 Dispose 方法完成。
有两种方法可以实现这一点:
2 的好处是您可以释放对订阅者的任何引用,从而启用收集(正如我之前所解释的,即使发布者是垃圾但属于较高代,它仍可能会延长较低代订阅者的收集时间)。
虽然,诚然,由于发布者的“隐藏”可达性,您很少会遇到所表现出的行为,但正如您所见,2 的好处是显而易见的,并且对所有事件发布者尤其是长期存在的事件发布者都有效(单身人士! !)。这本身就值得实施 Dispose 并使用 2。
归档时间: |
|
查看次数: |
15201 次 |
最近记录: |