.Net开发笔记(六)关于事件(续)
上一篇最后提到了怎么解决由“事件编程”引起的内存泄露问题,最后几句说到了由“弱引用”的概念引申出来“弱委托”。具体没说怎么去实现,这篇文章介绍一下具体实现过程。(请先看前一篇文章)
先来看一下MSDN上对Delegate(委托)的解释:
表示委托,委托是一种数据结构,它引用静态方法或引用类实例及该类的实例方法。
我们先不去管网上对“委托”的其他形象比如,比如“类似函数指针”、“对同一类方法的签名”等等。先来看看MSDN上的解释是个什么意思。首先它是一种数据结构,基本可以看做包含两个东西,一个是方法,一个是方法的所有者,即对象(静态方法的所有者为类)。那么我们可以简单的用图来画一下:
图1
如果对委托比较熟悉的人可能已经知道,所有我们定义的委托变量,都有两个属性Target和Method,这也正验证了上图的正确性。
那么Event(事件)是个什么东西?看一下MSDN对“事件”的一段解释:
在发生其他类或对象关注的事情时,类或对象可通过事件通知它们。发送(或引发)事件的类称为“发行者”,接收(或处理)事件的类称为“订户”。
这个解释基本上没什么用,因为它跟之前谈到的“委托”没任何联系,那我们再回忆一下我们之前在代码中是怎么去定义一个事件的?书上说网上也说,定义一个委托变量,在声明前面加一个event关键字,event关键字小写,那么这个委托变量就可以叫事件,编写代码时,VS IDE智能提示会在事件前面有一个“闪电”的图标。那么,根据这个描述,事件明显就是一种特殊的委托实例,没错,事件就是一种特殊的委托实例,它具备所有委托所具备的特点(有一些限制,跟主题没关系,略过)。
当事件的观察者(Observer)向事件的发布者(Subject)注册一个事件时,事件发布者就会产生一个对事件观察者的强引用(strong reference)(不然,当事件发生时,怎么才能通知观察者),也就是说,“注册事件”这个动作将事件的发布者和事件的观察者绑定到了一起,具体体现在:发布者包含了一个对观察者的强引用。
结合上一篇文章中的“图2”,我们可以看出,一个事件对应一个委托链,激发事件的过程可以看做:遍历这个委托链,在每一个节点中的Target上调用Method。这就完成了“事件发布者->激发事件->通知观察者->观察者响应事件->调用事件处理程序”这一过程。那么,对于一个事件,每被注册一次,它对应的委托链上就应该增加一个节点,该节点中存储着观察者的信息,由于同一个事件,同一个对象可能注册好几次,因此某一事件对应委托链中的节点的个数并不等于该事件的观察者数目,一般前者大于后者。再来看一张图:
图2
由此看出,委托链维持着事件发布者与事件观察者之间的联系,在某些情况下,当我们认为我们程序中的观察者已死(代码中再没有对观察者的引用),GC会回收观察者的内存时,实际情况往往不是我们认为的这样,由于委托链中仍然有对观察者的强引用,GC是不会对它进行内存回收的,这样一来,如果观察者数目大,观察者属于大对象,系统运行时间过长,就会导致内存不足。
注:GC回收对象内存的前提是程序中不再有该对象的强引用(strong reference)。
解决以上问题关键在于,怎么在观察者即将死亡的时候通知发布者将自己的引用删除,但是这个几乎做不到,因为你根本不知道观察者什么时候死亡,再者,当系统复杂庞大之后,你根本不知道谁是谁的观察者,谁又暗自拥有谁的强引用。所以,最好的情况是,观察者在必要的时候,能够正常的被GC回收内存,对于事件发布者来说,观察者的存在与否没有多大关系,如果观察者还存在,那么当事件发生时就通知观察者,如果观察者不存在了,那么当事件发生时就不通知观察者。这就要求事件发布者对于观察者之间的关联不能像之前那种“强引用”的关系,而应该是“弱引用”的关系。
因此,我们可以仿照原有的Delegate数据结构,模拟一种新的数据结构,将原来Target这个“强引用”变为“弱引用”,我们取名叫做“弱委托”(WeakDelegate),代码类似如下:
1 class WeakDelegate 2 { 3 WeakReference _weakTarget; 4 MethodInfo _method; 5 public WeakDelegate(WeakReference weakTarget, MethodInfo method) 6 { 7 _weakTarget = weakTarget; 8 _method = method; 9 } 10 public WeakReference WeakTarget 11 { 12 get 13 { 14 return _weakTarget; 15 } 16 } 17 public MethodInfo Method 18 { 19 get 20 { 21 return _method; 22 } 23 } 24 public object Invoke(params object[] parameters) 25 { 26 try 27 { 28 return _method.Invoke(_weakTarget.Target, parameters); 29 } 30 catch 31 { 32 return null; 33 } 34 } 35 }
包含两个属性WeakTarget和Method,分别对应原来委托的Target和Method属性,一个Invoke公共方法,对应原来委托的Invoke方法,用于执行委托。
由此一来,图2中委托链中的各个节点就可以由原来的Delegate(其派生类,我们自己定义的委托)变为我们现在的WeakDelegate,那么在写事件发布者类(Subject)的代码时,我们需要自己来维持这些委托链了,如下:
1 class Subject 2 { 3 protected Dictionary<object, List<WeakDelegate>> _delegateList = new Dictionary<object, List<WeakDelegate>>(); 4 5 private static object _XX1_KEY = new object(); 6 private static object _XX2_KEY = new object(); 7 private static object _XXn_KEY = new object(); 8 9 public event EventHandler XX1 10 { 11 add 12 { 13 if (_delegateList.ContainsKey(_XX1_KEY)) 14 { 15 _delegateList[_XX1_KEY].Add(new WeakDelegate(new WeakReference(value.Target), value.Method)); 16 } 17 else 18 { 19 List<WeakDelegate> list = new List<WeakDelegate>(); 20 list.Add(new WeakDelegate(new WeakReference(value.Target), value.Method)); 21 _delegateList.Add(_XX1_KEY, list); 22 } 23 } 24 remove 25 { 26 if (_delegateList.ContainsKey(_XX1_KEY)) 27 { 28 List<WeakDelegate> list = _delegateList[_XX1_KEY]; 29 List<WeakDelegate> del = new List<WeakDelegate>(); 30 foreach (WeakDelegate d in list) 31 { 32 WeakReference w = d.WeakTarget; 33 object tmp = w.Target; //先引用到w中的Target,因为GC是在自己的线程中回收垃圾 34 if (w.IsAlive) //再判断w.Target是否已被GC回收 35 { 36 if (w.Target == value.Target && d.Method == value.Method) 37 { 38 del.Add(d); 39 } 40 } 41 } 42 if (del.Count != 0) 43 { 44 foreach (WeakDelegate d in del) 45 { 46 list.Remove(d); 47 } 48 del.Clear(); 49 } 50 del = null; 51 } 52 } 53 } 54 public event EventHandler XX2 55 { 56 add 57 { 58 if (_delegateList.ContainsKey(_XX2_KEY)) 59 { 60 _delegateList[_XX2_KEY].Add(new WeakDelegate(new WeakReference(value.Target), value.Method)); 61 } 62 else 63 { 64 List<WeakDelegate> list = new List<WeakDelegate>(); 65 list.Add(new WeakDelegate(new WeakReference(value.Target), value.Method)); 66 _delegateList.Add(_XX2_KEY, list); 67 } 68 } 69 remove 70 { 71 if (_delegateList.ContainsKey(_XX2_KEY)) 72 { 73 List<WeakDelegate> list = _delegateList[_XX2_KEY]; 74 List<WeakDelegate> del = new List<WeakDelegate>(); 75 foreach (WeakDelegate d in list) 76 { 77 WeakReference w = d.WeakTarget; 78 object tmp = w.Target; //先引用到w中的Target,因为GC是在自己的线程中回收垃圾 79 if (w.IsAlive) //再判断w.Target是否已被GC回收 80 { 81 if (w.Target == value.Target && d.Method == value.Method) 82 { 83 del.Add(d); 84 } 85 } 86 } 87 if (del.Count != 0) 88 { 89 foreach (WeakDelegate d in del) 90 { 91 list.Remove(d); 92 } 93 del.Clear(); 94 } 95 del = null; 96 } 97 } 98 } 99 public event EventHandler XXn 100 { 101 add 102 { 103 if (_delegateList.ContainsKey(_XXn_KEY)) 104 { 105 _delegateList[_XXn_KEY].Add(new WeakDelegate(new WeakReference(value.Target), value.Method)); 106 } 107 else 108 { 109 List<WeakDelegate> list = new List<WeakDelegate>(); 110 list.Add(new WeakDelegate(new WeakReference(value.Target), value.Method)); 111 _delegateList.Add(_XXn_KEY, list); 112 } 113 } 114 remove 115 { 116 if (_delegateList.ContainsKey(_XXn_KEY)) 117 { 118 List<WeakDelegate> list = _delegateList[_XXn_KEY]; 119 List<WeakDelegate> del = new List<WeakDelegate>(); 120 foreach (WeakDelegate d in list) 121 { 122 WeakReference w = d.WeakTarget; 123 object tmp = w.Target; //先引用到w中的Target,因为GC是在自己的线程中回收垃圾 124 if (w.IsAlive) //再判断w.Target是否已被GC回收 125 { 126 if (w.Target == value.Target && d.Method == value.Method) 127 { 128 del.Add(d); 129 } 130 } 131 } 132 if (del.Count != 0) 133 { 134 foreach (WeakDelegate d in del) 135 { 136 list.Remove(d); 137 } 138 del.Clear(); 139 } 140 del = null; 141 } 142 } 143 } 144 145 public event EventHandler CommonEvent; 146 147 protected virtual void OnXX1(EventArgs e) 148 { 149 if (_delegateList.ContainsKey(_XX1_KEY)) //有事件注册者 150 { 151 List<WeakDelegate> list = _delegateList[_XX1_KEY]; 152 List<WeakDelegate> del = new List<WeakDelegate>(); 153 foreach (WeakDelegate d in list) //遍历委托链(集合) 154 { 155 object tmp = d.WeakTarget.Target; // 156 157 if (d.WeakTarget.IsAlive) //查找是否已被GC回收 158 { 159 d.Invoke(new object[] { this, e }); //通知观察者 160 } 161 else 162 { 163 del.Add(d); //添加到待删集合 164 } 165 } 166 if (del.Count != null) 167 { 168 foreach (WeakDelegate d in del) //移除已被GC回收的观察者 169 { 170 list.Remove(d); 171 } 172 } 173 } 174 } 175 protected virtual void OnXX2(EventArgs e) 176 { 177 if (_delegateList.ContainsKey(_XX2_KEY)) //有事件注册者 178 { 179 List<WeakDelegate> list = _delegateList[_XX2_KEY]; 180 List<WeakDelegate> del = new List<WeakDelegate>(); 181 foreach (WeakDelegate d in list) //遍历委托链(集合) 182 { 183 object tmp = d.WeakTarget.Target; // 184 185 if (d.WeakTarget.IsAlive) //查找是否已被GC回收 186 { 187 d.Invoke(new object[] { this, e }); //通知观察者 188 } 189 else 190 { 191 del.Add(d); //添加到待删集合 192 } 193 } 194 if (del.Count != null) 195 { 196 foreach (WeakDelegate d in del) //移除已被GC回收的观察者 197 { 198 list.Remove(d); 199 } 200 } 201 } 202 } 203 protected virtual void OnXXn(EventArgs e) 204 { 205 if (_delegateList.ContainsKey(_XXn_KEY)) //有事件注册者 206 { 207 List<WeakDelegate> list = _delegateList[_XXn_KEY]; 208 List<WeakDelegate> del = new List<WeakDelegate>(); 209 foreach (WeakDelegate d in list) //遍历委托链(集合) 210 { 211 object tmp = d.WeakTarget.Target; // 212 213 if (d.WeakTarget.IsAlive) //查找是否已被GC回收 214 { 215 d.Invoke(new object[] { this, e }); //通知观察者 216 } 217 else 218 { 219 del.Add(d); //添加到待删集合 220 } 221 } 222 if (del.Count != null) 223 { 224 foreach (WeakDelegate d in del) //移除已被GC回收的观察者 225 { 226 list.Remove(d); 227 } 228 } 229 } 230 } 231 protected virtual void OnCommonEvent(EventArgs e) 232 { 233 if (CommonEvent != null) 234 { 235 CommonEvent(this, e); 236 } 237 } 238 239 public void DoSomething() 240 { 241 //符合某一条件,激发事件 242 OnXX1(new EventArgs()); 243 OnXX2(new EventArgs()); 244 OnXXn(new EventArgs()); 245 OnCommonEvent(new EventArgs()); 246 } 247 }
在事件发布者中,我定义了三个使用我们自己WeakDelegate的事件,由_delegateList管理,分别是XX1、XX2、XXn事件,他们对应的委托链中的每一个节点属于WeakDelegate类型,不会保存观察者的强引用。另外为了对比,我定义了一个传统的使用Delegate的事件CommonEvent,它对应的委托链中的每一个节点属于Delegate(派生类)类型,保存观察者的强引用。
定义了一个观察者(Observer)类,代码为:
1 class Observer 2 { 3 public void RegisterXX1(Subject sub) 4 { 5 sub.XX1 += new EventHandler(sub_XX1); 6 } 7 public void RegisterXX2(Subject sub) 8 { 9 sub.XX2 += new EventHandler(sub_XX2); 10 } 11 public void RegisterXXn(Subject sub) 12 { 13 sub.XXn += new EventHandler(sub_XXn); 14 } 15 public void RegisterCommonEvent(Subject sub) 16 { 17 sub.CommonEvent += new EventHandler(sub_CommonEvent); 18 } 19 20 void sub_CommonEvent(object sender, EventArgs e) 21 { 22 Console.WriteLine("Common Event raised!"); 23 } 24 25 void sub_XXn(object sender, EventArgs e) 26 { 27 Console.WriteLine("XXn event raised!"); 28 } 29 void sub_XX2(object sender, EventArgs e) 30 { 31 Console.WriteLine("XX2 event raised!"); 32 } 33 34 void sub_XX1(object sender, EventArgs e) 35 { 36 Console.WriteLine("XX1 event raised!"); 37 } 38 }
该类除了注册Subject类的事件外,没有其他内容,当事件发生后,输出与该事件相关的信息。
测试代码如下:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //Subject sub = new Subject(); //事件发布者 6 //Observer obs = new Observer(); //事件观察者 7 //obs.RegisterXX1(sub); //观察者注册事件XX1,两者建立关联 8 9 10 //sub.DoSomething(); //发布者激发事件 11 ////输出XX1 event raised! 12 13 14 ////强制GC回收obs内存,结束obs的生命期 15 //obs = null; 16 //GC.Collect(); 17 //Console.WriteLine("obs is 'dead'?!"); 18 19 20 //sub.DoSomething(); //再次激发事件 21 ////obs已死,不会再响应XX1事件 22 23 24 //Console.Read(); 25 26 27 Subject sub = new Subject(); //事件发布者 28 Observer obs = new Observer(); //事件观察者 29 obs.RegisterXX1(sub); //观察者注册事件XX1,建立关联 30 obs.RegisterCommonEvent(sub); //观察者注册事件CommonEvent,建立关联 31 32 33 sub.DoSomething(); //事件发布者激发事件 34 //输出 XX1 event raised! 35 // CommonEvent event raised! 36 37 38 //强制GC回收obs内存,结束obs的生命期,但是徒劳!!! 39 obs = null; 40 GC.Collect(); 41 Console.WriteLine("obs is 'dead'?!"); 42 43 44 sub.DoSomething(); //再次激发事件 45 //obs并没有死去,会再次响应事件 46 //输出 XX1 event raised! 47 // CommonEvent event raised! 48 49 50 //因此,obs并没有我们期待的那样结束了自己的生命期 51 Console.Read(); 52 } 53 }
被注释部分事件发布者与观察者之间属于弱关联,观察者能够正常死亡;未被注释部分事件发布者与观察者属于强关联,观察者不能正常死亡。
运行结果如下:
图3
图4
本文代码均已调试通过,环境(XP+.net 3.5)。
本文所有事件,均没有考虑“多线程”情况。
希望有帮助,O(∩_∩)O~