zz962

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

地址:http://www.codeproject.com/Articles/29922/Weak-Events-in-C

 

 

Introduction

When using normal C# events, registering an event handler creates a strong reference from the event source to the listening object.

使用通常的C#事件时,注册一个事件handler创建了一个从事件源到监听对象的strong引用


If the source object has a longer lifetime than the listener, and the listener doesn't need the events anymore when there are no other references to it, using normal .NET events causes a memory leak: the source object holds listener objects in memory that should be garbage collected.

当源对象的生命周期比监听者长的时候,当没有其他对象引用的时候,监听者并不再需要事件。使用通常的.NET事件会导致内存泄漏:源对象还把持着监听对象,而它本该被收集掉。

There are lots of different approaches to this problem. This article will explain some of them and discuss their advantages and disadvantages. I have sorted the approaches in two categories: first, we will assume that the event source is an existing class with a normal C# event; after that, we will allow modifying the event source to allow different approaches.

对这个问题有很多种不同的解决方法。这篇文章会解释了其中的一部分并讨论它们的优缺点。首先,我们假设事件源是一个含有常规.NET事件的已存在的类,我们将允许修改事件源以遵循不同的方法

 

What Exactly are Events?

Many programmers think events are a list of delegates - that's simply wrong. Delegates themselves have the ability to be "multi-cast":

很多程序员错误地认为event是一个delegates的list:delegates本身有能力实现“多播”:

EventHandler eh = Method1;
eh += Method2;

So, what then are events? Basically, they are like properties: they encapsulate a delegate field and restrict access to it. A public delegate field (or a public delegate property) could mean that other objects could clear the list of event handlers, or raise the event - but we want only the object defining the event to be able to do that.

什么是event?简单地说,它们像属性:它们封装了一个delegate域并限制访问它。一个public delegate field意味着其他对象可以把event handler的列表清空,或者触发一个event。不过我们只希望定义事件的对象能做这些。

Properties essentially are a pair of get/set-methods. Events are just a pair of add/remove-methods.

属性本质上是一对get和set方法,Event是一对add和remove方法

public event EventHandler MyEvent {
add { ... }
remove { ... }
}

Only adding and removing handlers is public. Other classes cannot request the list of handlers, cannot clear the list, or cannot call the event.

只有添加和删除handler是public的。其他的类不能不能获得list,不能清除list,也不能调用evnet

Now, what leads to confusion sometimes is that C# has a short-hand syntax:

有时让人困惑的是C#的short-hand语法

public event EventHandler MyEvent;

This expands to:

private EventHandler _MyEvent; // the underlying field
// this isn't actually named "_MyEvent" but also "MyEvent",
// but then you couldn't see the difference between the field
// and the event.
public event EventHandler MyEvent {
  add { lock(this) { _MyEvent += value; } }
  remove { lock(this) { _MyEvent -= value; } }
}

Yes, the default C# events are locking on this! You can verify this with a disassembler - the add and remove methods are decorated with [MethodImpl(MethodImplOptions.Synchronized)], which is equivalent to locking on this.

Registering and deregistering events is thread-safe. However, raising the event in a thread-safe manner is left to the programmer writing the code that raises the event, and often gets done incorrectly: the raising code that's probably used the most is not thread-safe:

缺省的C#事件是在this上加lock的,可以通过反汇编查看。注册和注销事件是线程安全的,然而触发事件可能不是线程安全的

if (MyEvent != null)
   MyEvent(this, EventArgs.Empty);

上述代码有可能出现crash,在调用MyEvent的时候恰好被删除

The second most commonly seen strategy is first reading the event delegate into a local variable.

另外一种方法是在调用之前把handler保存在一个临时变量里

EventHandler eh = MyEvent;
if (eh != null) eh(this, EventArgs.Empty);

Is this thread-safe? Answer: it depends. According to the memory model in the C# specification, this is not thread-safe. The JIT compiler is allowed to eliminate the local variable, see Understand the Impact of Low-Lock Techniques in Multithreaded Apps [^]. However, the Microsoft .NET runtime has a stronger memory model (starting with version 2.0), and there, that code is thread-safe. It happens to be also thread-safe in Microsoft .NET 1.0 and 1.1, but that's an undocumented implementation detail.

这种方法线程安全么?回答是不一定,根据C#标准里的内存模型,这不是线程安全的。

A correct solution, according to the ECMA specification, would have to move the assignment to the local variable into a lock(this) block or use a volatile field to store the delegate.

根据ECMA规范,正确的解决方法是把对临时变量的赋值锁住

EventHandler eh;
lock (this) { eh = MyEvent; }
if (eh != null) eh(this, EventArgs.Empty);

 

This means we'll have to distinguish between events that are thread-safe and events that are not thread-safe.

这意味着我们必须要区分事件是否线程安全

Part 1: Listener-side Weak Events

In this part, we'll assume the event is a normal C# event (strong references to event handlers), and any cleanup will have to be done on the listening side.

在这部分,我们假设event是通常的C#event,任何clean up都在监听端来做

Solution 0: Just Deregister

 

void RegisterEvent()
{
    eventSource.Event += OnEvent;
}
void DeregisterEvent()
{
    eventSource.Event -= OnEvent;
}
void OnEvent(object sender, EventArgs e)
{
    ...
}

 

Simple and effective, this is what you should use when possible. But, often, it's not trivially possible to ensure the DeregisterEvent method is called whenever the object is no longer in use. You might try the Dispose pattern, though that's usually meant for unmanaged resources. A finalizer will not work: the garbage collector won't call it because the event source still holds a reference to our object!

简单有效,只要有可能你就应该这么做。

Advantages:Simple if the object already has a notion of being disposed.

优势:如果对象已有dispose概念的话比较简单

Disadvantages:Explicit memory management is hard, code can forget to call Dispose.

弊端:显式内存管理比较困难,容易忘记dispose

Solution 1: Deregister When the Event is Called

 

void RegisterEvent()
{
    eventSource.Event += OnEvent;
}

void OnEvent(object sender, EventArgs e)
{
    if (!InUse) {
        eventSource.Event -= OnEvent;
        return;
    }
    ...
}

 

Now, we don't require that someone tells us when the listener is no longer in use: it just checks this itself when the event is called. However, if we cannot use solution 0, then usually, it's also not possible to determine "InUse" from within the listener object. And given that you are reading this article, you've probably come across one of those cases.

现在,你不必要求他人告诉我们什么时候listener不再存在:它只是在事件被调用时自我检查。然而,如果我们不能使用方案0,通常也没办法判断InUse。

But, this "solution" already has an important disadvantage over solution 0: if the event is never fired, then we'll leak listener objects. Imagine that lots of objects register to a static "SettingsChanged" event - all these objects cannot be garbage collected until a setting is changed - which might never happen in the program's lifetime.

这种方案相对于方案0有一个重要的弊端:如果事件从来没有被触发,listener对象会被leak。想像一下,有很多对象注册了一个event----所有这些对象都不能被垃圾回收直到该事件被触发。

Advantages:None.

Disadvantages:Leaks when the event never fires; usually, "InUse" cannot be easily determined.

 

小结

posted on 2014-07-17 14:07  zz962  阅读(291)  评论(0编辑  收藏  举报