|
导读.NET中的委托—事件机制: 办公室的故事Chris Sells 著 ( <<ATL Internals>>一书作者之一,该书是ATL编程的宝典)Jackeygou 译 研发... .NET中的委托—事件机制: 办公室的故事Chris Sells 著 ( <<ATL Internals>>一书作者之一,该书是ATL编程的宝典) Jackeygou 译 研发中心 软件技术的动人美感来源于对现实世界的真实理解. 一— 译注 =========================================== 强类型耦合 ------------ 从前在我们这个城市的西南角,有一家小技术服务公司,公司里有一位聪明能干的年轻人,他的名字叫Peter。不幸的是他的老板却是一位吝啬、多疑,而且极为循规蹈矩的小人,例如下属的任何工作都必须先报告,而且经他审批后才能进行。可怜的Peter自然不愿他的老板整日里站在自己的身后虎视眈眈,于是他对老板保证,自己的任何工作进度都会向他及时通禀。Peter实现这一承诺的方法就是周期性的利用类型引用回调boss,把他老板叫过来审查。程序实现如下: class Worker { public void Advise(Boss boss) { _boss = boss; } public void DoWork() { Console.WriteLine("Worker: work started"); if( _boss != null ) _boss.WorkStarted(); // 开始工作的审批 Console.WriteLine("Worker: work progressing"); if( _boss != null ) _boss.WorkProgressing(); // 进行工作的审批 Console.WriteLine("Worker: work completed"); if( _boss != null ) { int grade = _boss.WorkCompleted(); // 完成工作的审批 Console.WriteLine("Worker grade= " + grade); } } private Boss _boss; } class Boss { public void WorkStarted() { /* 老板实际上并不很关心. */ } public void WorkProgressing() { /*老板实际上并不很关心. */ } public int WorkCompleted() { Console.WriteLine("It's about time!"); return 2; /* 满分10分,才给2分,够吝啬小气吧. */ } } class Universe { static void Main() { Worker peter = new Worker(); // 生成peter实例 Boss boss = new Boss(); // 生成boss实例 peter.Advise(boss); peter.DoWork(); Console.WriteLine("Main: 工作结束!"); Console.ReadLine(); } } 【译注:以下是上段程序输出结果: Worker: work started Worker: work progressing Worker: work completed It's about time! Worker grade = 2 Main: worker completed work 】 接口 ---------- 现在Peter已经成为一个特殊的成员,因为它不仅要忍受它那位吝啬老板的指使,而且还与Universe对象紧密相关(没办法谁让他身不逢时处于Universe类的Main函数中)。这种“亲密接触”使Peter觉得Universe对他的工作进度也很感兴趣。不幸的是,除了保证boss能够被通知外,如果不为Universe添加一个特殊的通知方法和回调,Peter无法向Universe通知其工作进度。而Peter首先要做的就是从具体的通知执行过程中分离出隐藏的通知约定,而这就需要定义接口了: interface IWorkerEvents { void WorkStarted(); void WorkProgressing(); int WorkCompleted(); } class Worker { public void Advise(IWorkerEvents events) { _events = events; } public void DoWork() { Console.WriteLine("Worker: work started"); if( _events != null ) _events.WorkStarted(); Console.WriteLine("Worker: work progressing"); if(_events != null ) _events.WorkProgressing(); Console.WriteLine("Worker: work completed"); if(_events != null ) { int grade = _events.WorkCompleted(); Console.WriteLine("Worker grade= " + grade); } } private IWorkerEvents _events; } class Boss : IWorkerEvents { public void WorkStarted() { /* boss doesn't care. */ } public void WorkProgressing() { /* boss doesn't care. */ } public int WorkCompleted() { Console.WriteLine("It's about time!"); return 3; /* out of 10 */ } } 【译注:以下是上段程序输出结果: Worker: work started Worker: work progressing Worker: work completed It's about time! Worker grade = 3 Main: worker completed work 】 委托 ----------- 不幸的是,Peter整日忙于通知他的老板去负责执行接口,而无暇顾及通知Universe。但是Peter觉得这已经不远了,因为至少他已经成功的将对具体老板的引用,抽象为对统一的IWorkerEvents接口的引用了。换句话说,别的实现了IWorkerEvents接口的什么人都可以收到工作进度通知。 就这样,过了一段时间,Peter的老板依然对Peter很不满,他大声的抱怨到:“Hi! Peter,你知道不知道,你真的很烦呀!为什么你每次在开始工作和工作进行时都要叫上我,我并不是每次都对这些过程感兴趣。你不仅强迫我做一些我不感兴趣的事情,而且你还浪费了大量宝贵的工作时间在等我从审批事件中返回。如果我很忙,不能做出回答,难道你就要放假不成?你能不能够别这样来打搅我,OK?” 此时,Peter逐渐意识到采用接口在很多时候时是非常有用的(译注:接口在COM世界里,可以称得上是万物之本),但是用接口来处理事件时,就有些粒度(译注:不是力度)不够精细。他希望在事件发生时只通知哪些对该事件感兴趣的人,而不是让一个人必须对所有的事件感兴趣。所以,Peter将接口进一步肢解成更小的独立的委托函数,每一个委托函数可以看作是一个轻量级的函数接口,意义等价于类接口。 delegate void WorkStarted(); delegate void WorkProgressing(); delegate int WorkCompleted(); class Worker { public void DoWork() { Console.WriteLine("Worker: work started"); if( started != null ) started(); Console.WriteLine("Worker: work progressing"); if( progressing != null ) progressing(); Console.WriteLine("Worker: work completed"); if( completed != null ) { int grade = completed(); Console.WriteLine("Worker grade= " + grade); } } public WorkStarted started; public WorkProgressing progressing; public WorkCompleted completed; } class Boss { public int WorkCompleted() { Console.WriteLine("Better..."); return 4; /* out of 10 */ } } class Universe { static void Main() { Worker peter = new Worker(); Boss boss = new Boss(); peter.completed = new WorkCompleted(boss.WorkCompleted); peter.DoWork(); Console.WriteLine("Main: worker completed work"); Console.ReadLine(); } } 【译注:以下是上段程序输出结果: Worker: work started Worker: work progressing Worker: work completed Better... Worker grade = 4 Main: worker completed work 】 【译注:对“但是用接口来处理事件时,就有些粒度不够精细。”的理解可用下例说明,请仔细观察一下程序,如果全部换作接口定义去处理,思考一下这样做的不利之处。欢迎大家讨论: using System; interface IWorkStartedEvent { void WorkStarted(); } interface IWorkProgressingEvent { void WorkProgressing(); } interface IWorkCompletedEvent { int WorkCompleted(); } class Worker { public void Advise(IWorkCompletedEvent AEvent) { _event = AEvent; } public void DoWork() { Console.WriteLine("Worker: work completed"); if(_event != null ) { int grade = _event.WorkCompleted(); Console.WriteLine("Worker grade = " + grade); } } private IWorkCompletedEvent _event; } class Boss : IWorkCompletedEvent { public int WorkCompleted() { Console.WriteLine("Better..."); return 4; /* out of 10 */ } } class Universe { static void Main() { Worker peter = new Worker(); Boss boss = new Boss(); peter.Advise(boss); peter.DoWork(); Console.WriteLine("Main: worker completed work"); Console.ReadLine(); } } 以下是上段程序输出结果: Worker: work completed Better... Worker grade = 4 Main: worker completed work 】 静态响应函数 --------------- 这样一来,果然立竿见影。Peter再也不会因为开始工作审批等事件而去打搅他的老板。但是现在的问题是Peter依然没有将Universe列入他的事件响应者名单。因为Universe是一个全封闭实体类(all-compassing entity),所以,如果需要与委托相连,就要实例化Universe,实在没必要(设想一下Universe的多个实例需要多少资源…)。取而代之,采用静态函数实现。因为委托支持这些静态函数定义: class Universe { static void WorkerStartedWork() { Console.WriteLine("Universe notices worker starting work"); } static int WorkerCompletedWork() { Console.WriteLine("Universe pleased with worker's work"); return 7; } static void Main() { Worker peter = new Worker(); Boss boss = new Boss(); peter.completed = new WorkCompleted(boss.WorkCompleted);// # peter.started = new WorkStarted(Universe.WorkerStartedWork); peter.completed = new WorkCompleted(Universe.WorkerCompletedWork); // 这一行使得前面#行白费了!boss被Universe取代了,也许Peter更愿意如此。 peter.DoWork(); Console.WriteLine("Main: worker completed work"); Console.ReadLine(); } } 【译注:以下是上段程序输出结果: Worker: work started Universe notices worker starting work Worker: work progressing Worker: work completed Universe pleased with worker's work Worker grade = 7 Main: worker completed work 】 事件 ---------- 事与愿违,虽然Peter很乐意让Universe参与合作,但Universe自身很忙也不习惯将自己的全部身心都投入到单一的Peter实例中,以取代Peter老板委托的位置。此外Peter类中的委托字段是公有访问权限也会存在一些未知的负面作用。例如,那天Peter的老板想不开,突然决定要亲自激活Peter的委托任务,也尚未可知。(按此人的多疑易怒的性情是很有可能的!) // Peter的老板自己将委托任务激活,这就是公有权限的恶果!!! if( peter.completed != null ) peter.completed(); 可怜的Peter自然不愿意上述的任何一种情况发生,他需要实现对于每一个委托都能加入注册和反注册函数,只有这样事件响应者可以添加和删除自身, 但谁都不能够清空整个事件列表,避免出现刚才Universe将boss取而代之的结果。Peter自身对此是无能为力了,但是通过C#提供的“event”关键字,Peter就可以梦想成真了: class Worker { ... public event WorkStarted started; public event WorkProgressing progressing; public event WorkCompleted completed; } Peter知道利用C#的委托—事件机制可以轻松搞定,而且这时event关键字提供了一个关于委托的属性(property),可以允许C#客户方便地通过“+=”和“-= ”添加和删除他们自定义的执行函数,而不像委托那样挂上就甩不掉: static void Main() { Worker peter = new Worker(); Boss boss = new Boss(); peter.completed += new WorkCompleted(boss.WorkCompleted); peter.started += new WorkStarted(Universe.WorkerStartedWork); peter.completed += new WorkCompleted(Universe.WorkerCompletedWork); peter.DoWork(); Console.WriteLine("Main: worker completed work"); Console.ReadLine(); } 【译注:以下是上段程序输出结果 Worker: work started Universe notices worker starting work Worker: work progressing Worker: work completed Better...// 【译注:boss也通知到啦,刚才打‘#’那代码被执行了。但是且慢,boss打的那4分没有得到,后面只得到了Universe给的7分】 Universe pleased with worker's work Worker grade = 7 Main: worker completed work 】 查询所有结果 -------------- 对于这一点,Peter满怀信心。他可以方便管理所有与事件相关的执行者信息,而不需要与具体的执行者产生紧耦合关系。Peter注意到他的最终工作评定(completed事件)关联了两个事件执行函数:boss和Universe。他们都给Peter一个评定分数4分和7分。正是春风得意的Peter自然不会随意丢掉任何一个结果,他希望能得到每一个响应者的评分结果。因此,他决定提取委托调用列表,以便手工分别调用它们并累加,这样他的Money…. public void DoWork() { ... Console.WriteLine("Worker: work completed"); int grade = 0; if( completed != null ) { foreach( WorkCompleted wc in completed.GetInvocationList() ) { grade += wc(); Console.WriteLine("Worker grade= " + grade); } } } 【译注:以下是上段程序输出结果 Worker: work started Universe notices worker starting work Worker: work progressing Worker: work completed Better... Worker grade = 4 【译注:boss打的4分也得到啦】 Universe pleased with worker's work Worker grade = 11【译注:Peter共得了11分!】 Main: worker completed work 】 异步通知:激活和放弃 ------------------------ Peter很快又发现了新的问题,他的老板和Universe有时都会出现正为别的事忙碌而无暇顾及Peter的工作评定,这就使得Peter的工作评定时间被拖延了: class Boss { public int WorkCompleted() { System.Threading.Thread.Sleep(3000); // 延时3秒 Console.WriteLine("Better..."); return 6; /* out of 10 */ } } class Universe { static int WorkerCompletedWork() { System.Threading.Thread.Sleep(4000); // 延时4秒 Console.WriteLine("Universe is pleased with worker's work"); return 7; } ... } 噢!看到了,这确实很容易造成时间的浪费,而且最让Peter恼火的是,周末他与Kristin的约会都被搅黄了。不行,这一定要改正!于是Peter决定放弃工作评定打分而改为异步委托—事件激活: public void DoWork() { ... Console.WriteLine("Worker: work completed"); if( completed != null ) { foreach( WorkCompleted wc in completed.GetInvocationList() ) { wc.BeginInvoke(null, null); } } } 【译注:以下是上段程序输出结果 Worker: work started Universe notices worker starting work Worker: work progressing Worker: work completed Main: worker completed work //【译注:由于是异步触发事件,因此这一行先输出啦】 Better... //【译注:评分已被忽略】 Universe pleased with worker's work //【译注:评分已被忽略】 】 异步通知:缓存池(Polling) ------------------------------- 这就使得Peter可以通知监听者的同时自己也能立即返回工作,让进程的线程池调用委托。然而不久他就发现监听者对其工作的评分丢掉了。这还了得!没有评定分数,我的绩效…Peter简直欲哭无泪.他立即采用缓存池(Polling)机制。Peter知道他做了一件明智的事并乐意Universe作为一个整体(不单单是他的boss)评判他。因此,Peter异步触发事件,但定期轮询,以察看可以获得的评分: public void DoWork() { ... Console.WriteLine("Worker: work completed"); if( completed != null ) { foreach( WorkCompleted wc in completed.GetInvocationList() ) { IAsyncResult res = wc.BeginInvoke(null, null); while( !res.IsCompleted ) System.Threading.Thread.Sleep(1); int grade = wc.EndInvoke(res); Console.WriteLine("Worker grade= " + grade); } } } 【译注:以下是上段程序输出结果 Worker: work started Universe notices worker starting work Worker: work progressing Worker: work completed Better... Worker grade = 6 Universe pleased with worker's work Worker grade = 7 Main: worker completed work //【译注:注意这个结果到最后才输出,下一节首句意思即是如此】 】 异步通知: 委托 ---------------- 不幸的是,Peter又倒退了—就象他一开始想避免boss站在一旁边监视他一样。也就是说,他现在要监看整个工作过程。因此,peter决定使用自己的委托作为异步委托完成时的通知方式,这样他就可以立即回去工作,而当工作被打分时,仍然可以接到通知: public void DoWork() { ... Console.WriteLine("Worker: work completed"); if( completed != null ) { foreach( WorkCompleted wc in completed.GetInvocationList() ) { wc.BeginInvoke(new AsyncCallback(WorkGraded), wc); } } } private void WorkGraded(IAsyncResult res) { WorkCompleted wc = (WorkCompleted)res.AsyncState; int grade = wc.EndInvoke(res); Console.WriteLine("Worker grade= " + grade); } 【译注:以下是上段程序输出结果 Worker: work started Universe notices worker starting work Worker: work progressing Worker: work completed Main: worker completed work //【译注:异步委托发生了效果,因此这一行先输出啦】 Better... Worker grade = 6 Universe pleased with worker's work Worker grade = 7 】 圆满结局 ----------- Peter、他的老板和Universe最终皆大欢喜,Peter的老板和Universe分别负责他们各自感兴趣的事件。这就减轻了执行负担和不必要的来回调用时间。Peter负责在发生事件时通知与事件关联的执行者,并最终异步获得结果,而不管他们做出回应时间的长短。但是,Peter也深知这一切并不像外表看起来那么简单,因为当他异步激活一个事件,与该事件关联的执行函数很可能是在另一个线程中执行,由此造成潜在的调度处理问题。不过Peter与Mike是好朋友,而Mike则精通线程问题的处理,他会为Peter不遗余力的。 现在一切OK!,关于Peter的故事就讲到这里,如果大家感兴趣的话,我还有很多故事要讲的! |
温馨提示:喜欢本站的话,请收藏一下本站!