地址:http://www.codeproject.com/Articles/29922/Weak-Events-in-C程序員
When using normal C# events, registering an event handler creates a strong reference from the event source to the listening object.sass
使用一般的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.app
當源對象的生命週期比監聽者長的時候,當沒有其餘對象引用的時候,監聽者並再也不須要事件。使用一般的.NET事件會致使內存泄漏:源對象還把持着監聽對象,而它本該被收集掉。ide
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.ui
對這個問題有不少種不一樣的解決方法。這篇文章會解釋了其中的一部分並討論它們的優缺點。首先,咱們假設事件源是一個含有常規.NET事件的已存在的類,咱們將容許修改事件源以遵循不一樣的方法this
Many programmers think events are a list of delegates - that's simply wrong. Delegates themselves have the ability to be "multi-cast":spa
不少程序員錯誤地認爲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.rest
什麼是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.
這意味着咱們必需要區分事件是否線程安全
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都在監聽端來作
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
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.