Events, references, garbage collecting, memory leaks and weak delegates

20091209 标题更新更改了标题,囊括了原作者的另一篇短文

在关于服务、容器的编程(IOC实现)中,随着事件的引入,事情就会变的复杂起来。你需要把两个服务通过事件连接起来。但在平静的表面下,隐藏着可怕的内存泄露。事件并不适合于松散耦合的环境,值得大家格外注意。

这就是所谓的[url=http://www.google.com/search?q=%22lapsed+listener%22]lapsed listener[/url]问题

下边是摘自Steve Maine关于[url=http://hyperthink.net/blog/CommentView,guid,0b59471c-c6fc-4ce0-87c1-5da77dc2c4ea.aspx]该问题[/url]的一段话:

[quote]当对象订阅了一个事件却导致了它超出了(程序预期的)控制范围,失控的监听器(lapsed listener)就会产生——该对象不会被GC回收,因为它的引用始终存在于事件的Invoke列表当中。所有的事件订阅者都会被GC认为仍然存在引用。因此它们不会被回收,直到事件本身的消失(通常是程序关闭的时候)。举例来说,当你实现一个观察者模式时,你就必须小心处理相关事件和其订阅者的生命周期。否则,那些对象就会超出你的预期,长时间地驻留。这时候Unsubscribe()就是你的朋友了。[/quote]

事实上,.Net委托(delegate)就是一种观察者模式的实现。下边是一个对象可以被正确释放的例子:
StoopidObject object = new StoopidObject();
GC.Collect();
GC.WaitForPendingFinalizers();


下边的例子中虽然没有明显的队Observer的引用,但它却不能被释放:
Observer observer = new Observer();
Subject subject = new Subject();
subject.SomethingHappened += new EventHandler(observer.subject_SomethingHappened);
GC.Collect();
GC.WaitForPendingFinalizers();


一些社区里的朋友想出了一种“弱委托(weak delegates)”的方法:
[list]
[*][url=http://blogs.msdn.com/greg_schechter/archive/2004/05/27/143605.aspx]Greg Schechter[/url]
[*][url=http://www.interact-sw.co.uk/iangblog/2004/06/06/weakeventhandler]Ian Griffiths[/url] and [url=http://www.seedindustries.com/blog/x/2004_06_01_archive.html#108656795427367272]Xavier Musy[/url]
[/list]
原文作者采用了另外的方法,因为以上的方案使用了弱委托,但不能满足要求:在被GC回收之前,监听器还会不断收到事件的通知,你也不知道什么时候才会下一次GC。所以,在弱委托的方案中你必须接受你的对象不会立刻被释放这一事实。

原作者想要立刻停止受到事件通知,要求监听器主动断开连接。一种简单的方案就是在Dispose方法中完整该动作,因为在你想要释放一个对象的时候,你总会去调用Dispose方法。

下边的代码摘自原作者的测试代码:
另外作者在另一篇文章中提到了一种WeakDelegate的实现,这里只提供基本的实现代码仅供参考:


public class Subject
{
private ArrayList _Delegates;

public Subject() { }

public event EventHandler SomethingHappened
{
add
{
if (_Delegates == null)
_Delegates = new ArrayList(1);

// Reference the subscriber via a "short" weak reference. If the
// subscriber is not reachable by any other rooted reference path,
// it will be collected!
WeakReference wr = new WeakReference(value);
_Delegates.Add(wr);
}
remove
{
if (_Delegates == null)
return;

// Loop through the list of subscribers, removing any whose
// instance-identity matches the unsubscribe-request. Also
// take the opportunity, while we're at it, to remove any
// deceased weak refs. (Iterate in reverse, to avoid skipping.)
for (int i = _Delegates.Count-1; i >= 0; --i)
{
WeakReference wr = (WeakReference) _Delegates[i];
if (!wr.IsAlive || Object.ReferenceEquals(value, wr.Target))
_Delegates.RemoveAt(i);
}
}
}

public void Notify()
{
Console.WriteLine("Notifying");

if (_Delegates == null)
return;

// Enumerate the list of delegates; dereference and invoke each one.
for (int i = _Delegates.Count-1; i >= 0; --i)
{
WeakReference wr = (WeakReference) _Delegates[i];
if (wr.IsAlive)
{
EventHandler handler = wr.Target as EventHandler;
if (handler != null)
handler(this, EventArgs.Empty);
}
else
{
_Delegates.RemoveAt(i);
}
}
}
}



[size=x-large]强制取消事件订阅[/size]
[url=http://weblogs.asp.net/fmarguerie/archive/2009/09/09/forcing-event-unsubscription.aspx]原文地址: Forcing event unsubscription[/url]

根据笔者经验,事件是.net程序中的主要资源泄漏原因。开发人员应该对其进行两倍甚至三倍的检查。每当你向一个事件添加订阅时,你就应当想到需不需要或者什么时候去取消这个订阅。如果该订阅必须摘除(否则会引起泄漏),那就趁你还没忘记以前马上添加取消订阅的代码。

通常情况下,可以在Dispose方法中完成。

不要忘记事件发起者(Subject)将始终保持其观察者(Observer/Listenser)的引用(造成其不被释放):
[img]http://i.msdn.microsoft.com/ee658248.image006(en-us).jpg[/img]

为了保证Listenser被释放,推荐的做法是(主动)取消事件订阅。但当你已经明确知道某个事件不会再发送通知消息并希望事件的订阅者能够被释放,你可以通过以下代码达到目的:


if (SomeEvent != null)
{
foreach (EventHandler handler in SomeEvent.GetInvocationList())
SomeEvent -= handler;
}


[size=large][b]更简单地:[/b][/size]
SomeEvent = null
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值