首先,张子扬先生的这是两篇关于委托和事件间关系的文章,是目前为止我读过的介绍委托和事件以及异步调用最简明清晰文章,作者通过非常有节奏的“标题”->“问题”->“思路”->“实现”->“讲解”的结构,分步骤一步一步地将委托和事件的实现、应用与原理阐述得非常清楚,并且在行文期间将自己有趣的思考过程通过生动的语言表达了出来,使人读起来越发的感兴趣,以下就是我读过这两篇文章以后,对委托、事件、异步调用一些新的理解角度的阐述。
(推荐的张子扬先生的文章链接在本文末尾处,大家完全可以先不读我的故事,先去看那两篇文章,真的非常好玩)
首先要引用作者文中的一个总结语:
委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。
我觉得这句话可以从clr-via 设计的理念去阐述我的理解:
委托这个概念存在的作用就是实现了观察者模式,并在封装时用类的命名定义了方法的类型,代表了一类参数列表相同的方法。
对,没错,就是将各种不同命名的同参数列表的方法,进行了具有可以归类批量处理能力的封装,归类后就可以很方便的把标注了delegate这个关键字的具有特定参数列表的方法标记为一个类,并在所有做了订阅操作(+=)的方法,放置于其编译后所生成的代理类中的一个List<T>中,那么在调用的时候,就可以简单地使用调用delegate的一个实例成员,来通过一次输入代理所定义的参数列表,调用订阅了这一委托实例的这一组同类型方法,因为他们需要的参数列表都是相同的嘛~。
那么,c#委托到底是怎么个事儿呢?
先讲个故事,传说中有这么一个市(程序域),故事里的主人公就是你(主线程),市里有100个手工艺人(拥有可以订阅委托的订阅函数的类),这100个人里每个人都有用某种指定的材料和工具做出自己最擅长的作品(参数列表相同),这个时候,你发现这些工具和材料都有渠道可以找到,于是,你就想如果把材料变成作品,该有多大市场啊,本着商人逐利的思维,你对外宣布了这些手工艺人可以找你要材料和工具去制作他自己的作品(初始项目需求)。
于是,你就开始想办法准备搜罗的这些指定材料和工具,这就是在定义委托类型的参数列表,于是你需要为这件事起个业务名,就可以抽象为一种委托类型,随便起个名字吧就叫delegate SendToolandMaterial(Tools,Material)好了。
你清楚地知道自己要找的工具和材料类型了,并且找好了进货渠道,所以你可以开始往外公开你可以为需要的人搜集发放材料和工具,大家可以找你来要领取,而你公开消息的这个事儿,就是定义一个属于你类成员的委托实例(public SendToolandMaterial sendToolMaterial;)然后,外面得到你的这个消息了(看到公共成员sendToolMaterial),于是开始有人告诉你他要你的工具材料,因为他能做这种材料的作品,这个行为,就是订阅操作“+=”,实际上就是在编译运行时,将订阅函数加入了委托列表。
委托调用
在你发了一阵工具和材料之后(也有可能一个人都没发出去呢),这时由于你没说过什么时候让手艺人完成,于是随便一个知道你发工具和材料这事的人,都可以向你要求看看货(调用你的委托实例) ,就是要你把之前你发过工具和材料的艺人的作品拿回来给他看看,再决定买不买。于是你看见生意来啦~,就兴奋地挨着个儿地找之前找你要过工具和材料的匠人要作品(执行订阅函数列表)。
由于是同步调用,所以你(主线程)就只能一个一个找人,找到一个就要让这个收到材料和工具的匠人开始制作,等他做完,再去下一个人那儿收货,所以你得到所有作品所消耗的时间就是所有匠人做出作品的时间之和,有可能这时人家顾客已经等超时,就走了,有可能你之前发的这几个匠人做的作品都不符合他要求,但你只发了这几个人,于是有几单你耗了时间货还压在你手里,好悲催~不过还是成了几单赚了些小钱。
这时,你觉得自己的成品不足,不能满足所有来看货客人的审美需求,你觉得应该有计划地去把货都取来,然后再开始张罗客人看货,这样可以卖出最多的作品,于是你就开始盘算了……
c#事件Event与委托delegate的关系
故事继续,还是这个能发工具和材料的你,还是这个市,经历了上面挨个收作品的这个事,你觉得随便一个认识你的人都可以过来要求你中断你自己的事去要作品(执行委托)这一点非常不爽(其实挨个收作品很耗时间,这也让顾客很不满意,但你并没有意识到这也急需改进),所以,你决定不让外人有权这样要求你,于是你就对外边说了,虽然我发放了工具和原料,但是我只在我公开展示的时候,大家才能来进行参观交易(事件对委托的内部封装)。由于是内部决定的执行计划,别人说什么时候要参观就不管用了,这样,你就把之前发工具材料的那个委托(delegate)封装成了一个事件(event),发放工具变得随时可以发放(对外定义是public event),但什么时候去收作品这个事儿就变成你自己说了算了(编译时会变成private),看你心情了,也许你觉得发50个就可以收一圈,也许原材料不足,只能发30人份就得收一圈,这取决于你想在哪个结点取执行这个委托列表,所以你每次都能把匠人或原料利用完全。
所以,事件的原理其实就是委托,在编程的时候也完全能用委托去实现订阅和批量调用,只不过事件将委托封装成了内部的调用特性(至于事件怎么调用怎么定义,以及编译后变成什么代码,可以去那两篇美文里扣,我这儿只讲故事~)。
由于应用了内部事件,没有人打扰你分发任务了,于是慢慢你就觉得,这么干还真是特么够慢啊……,100个人我挨着个要,这不傻一漏么?而且,别人中途找我要东西,我也顾不上理他啊,我(主线程)还在外面挨着个收东西呢,这不气跑了客人才怪。不行,我得换个套路做买卖。
遇到问题之后,你开始了思考,于是,异步执行的概念诞生了
前思后想,你觉得发完了以后,告诉他们(MyEvent.GetInvocationList())一块儿开始工作(beginInvoke(toolobj , meterailobj , null, null)),弄完我再收作品(IAsyncResult )不得了么?就算我不把工具和材料全发完了,谁领了东西谁就开始,我给他个快递电话,弄完给我递过来不得了?(于是你养活了这里的快递行业)?这时候反正他开始了,谁来找我要我就专门等他那个干完给我(IAsyncResult asyncResult = del.BeginInvoke(2,5,null,null); 这样我就能在他们干活的时候继续干我自己的事了(主线程任务),就算中间我想做交易,我就去催特定的人给我他的作品(EndInvoke(asyncResult),然后再干我别的事儿也行啊,这时候别人也没停止工作(线程池(Thread.CurrentThread.IsThreadPoolThread)),这效率多高!
于是你开始,采用这个新的商业策略,你把做生意的理念改了,一有材料往外发,作品都由匠人领了就开始创作,要么就在是收快递,要么就是在见客户做交易,效率高了,声意也越来越红火,忙得好生了得啊!
异步的回调以及异步调用传入数据
故事继续。干了好一阵生意,你发现你自己已经熟知了市场行情,也对艺术有了一定的鉴赏力,于是你就琢磨着怎样让这帮匠人拿回材料工具后,能按照自己的意思去弄出作品,于是你开始琢磨着每次派发工具和材料的同时再给匠人一些主题之类的信息,这样就充分利用了手头的工匠资源,能更让出活的作品符合当前市场的预期。
于是你就这么干了,每次发了工具在手艺人表示可以开始干((beginInvoke(null, EventArgs.Empty, null, null) )的时候,你就来劲了,大吹一通你现在的灵感,多么多么希望达到一种效果这才是艺术之类的牛,然后你就让匠人深受感染,根据你的思想感情(string data = "Any data you want to pass.";)去创作作品。由于是按照你的意思来的,所以在做完之后,你将保有修改作品的权利 (AsyncCallback callBack = new AsyncCallback(OnAddComplete);),并为此塞给了一笔额外的钱(参数处理的时间开销)给匠人。于是匠人原来的创作模式变了,硬生生塞入了你的意思以后,变成了出品后你还能修改的作品(del.BeginInvoke(toolobj,meterailobj , callBack, data);)。
获得了高度的CPU线程并发效率和*修改结果的权利之后,你对这种新的工作模式相当得意,并乐此不疲得开始将所有的想法付诸于作品的产生,疯狂地对原有合作的匠人们输入个性化的意见(超级多样化的传入参数),并且经常批评不能处理好你的意见的工匠,认为他们做出来的作品不行,在得到作品后大量进行修改(为匹配需求写了很多的衍生回调函数)……
异步的好处
我认为异步最大的好处,就是主线程可以像故事里做生意的主人公抓人同时做事那样,抓出线程池中可用的资源及时执行(最顶上那两篇文章里面已经详细解释过是怎样的一个情景了,而且写得非常易懂,我就不画蛇添足解释了),然后执行的过程、结果以及对结果的控制,都通过beginInvoke 返回的 IAsyncResult,传入参数AsyncCallback 以及传入的数据参数,进行了很好的封装,可以使单线等待任务变为并发线程任务,很好得封装了一组线程协作的实现。
最后我为故事编了个皆大欢喜的结尾:
很长一段时间,匠人们跟着你的意思做事,并且做出来的东西被你根据自己的审美进行了许多修改,你愈发沉迷于高效率与自我实现的快感中,由于每次都需要额外付一大笔钱给匠人,而且越难的主题越贵,导致了开销大幅增长(传入参数类型多变产生的订阅参数修改开销以及额外的业务回调开销),再由于对手都是各种胡吹各种挖墙脚的竞争(更多的同种类异步调用),市场变化很快,并且由于你有了钱了就学坏,出入灯红酒绿的场所,口无遮拦得吹嘘自己业务的处理能力以及自己的艺术鉴赏力(认为服务器性能够用而开始盲目扩展业务,导致单节点性能瓶颈),名声就渐渐变差,有钱人买你的东西觉得有点掉价,于是买的人也少了,投入小于产出,很快你自己的产业就败落了(服务能力下降,导致服务废弃),由拥有更好名声更好经营习惯合作无间的商人群体取代了(谨慎扩展业务,不断完善架构最终催生出了分布式服务架构),真是皆大欢喜啊~。
附:张子扬先生文章链接: