首先,張子陽先生的這是兩篇關於委托和事件間關系的文章,是目前為止我讀過的介紹委托和事件以及異步調用最簡明清晰文章,作者通過非常有節奏的“標題”->“問題”->“思路”->“實現”->“講解”的結構,分步驟一步一步地將委托和事件的實現、應用與原理闡述得非常清楚,並且在行文期間將自己有趣的思考過程通過生動的語言表達了出來,使人讀起來越發的感興趣,以下就是我讀過這兩篇文章以后,對委托、事件、異步調用一些新的理解角度的闡述。
*由於后半講的賣藝術品故事只是在用主人公的行為模擬主線程處理函數調用的情景,所以為了能在讀的時候更有趣味,強烈建議新手在看這個故事前,先去補一補異步中的幾個概念 IAsyncResult、CallBack、Invoke、beginInvoke、endInvoke等基礎,上面兩篇文章就不錯。
(推薦的張子揚先生的文章鏈接在本文開始處,大家完全可以先不讀我的故事,先去看那兩篇文章,真的非常好玩)
首先要引用作者文中的一個總結語:
委托是一個類,它定義了方法的類型,使得可以將方法當作另一個方法的參數來進行傳遞,這種將方法動態地賦給參數的做法,可以避免在程序中大量使用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 以及傳入的數據參數,進行了很好的封裝,可以使單線等待任務變為並發線程任務,很好得封裝了一組線程協作的實現。
最后我為故事編了個皆大歡喜的結尾:
很長一段時間,匠人們跟着你的意思做事,並且做出來的東西被你根據自己的審美進行了許多修改,你愈發沉迷於高效率與自我實現的快感中,由於每次都需要額外付一大筆錢給匠人,而且越難的主題越貴,導致了開銷大幅增長(傳入參數類型多變產生的訂閱參數修改開銷以及額外的業務回調開銷),再由於對手都是各種胡吹各種挖牆腳的競爭(更多的同種類異步調用),市場變化很快,並且由於你有了錢了就學壞,出入燈紅酒綠的場所,口無遮攔得吹噓自己業務的處理能力以及自己的藝術鑒賞力(認為服務器性能夠用而開始盲目擴展業務,導致單節點性能瓶頸),名聲就漸漸變差,有錢人買你的東西覺得有點掉價,於是買的人也少了,投入小於產出,很快你自己的產業就敗落了(服務能力下降,導致服務廢棄),由擁有更好名聲更好經營習慣合作無間的商人群體取代了(謹慎擴展業務,不斷完善架構最終催生出了分布式服務架構),真是皆大歡喜啊~。
后記:
為什么要寫出這樣一篇故事一樣的文章呢?因為我注意到一個現象,就是有時一個概念或一個道理我覺得自己懂了,但是拿起來想寫出來卻寫不出來,想給別人說卻不知從何說起,直到我看到netfocus的enode群里面說的一句話(原話詳細有出入,大概意思)“只有你把你覺得理解了的東西能寫出來,你才能確定自己是理解了,而寫出來的東西分享出去還能讓別人看懂,那才說明了你寫的是對的”,我還想在這句話后面加一句,你寫的別人能懂,那是義務,畢竟占用了別人生命中的時間和注意力,要是別人還能喜歡你的作品,那就是緣分了。所以我現在的小目標之一,是想從昨天開始把我理解了的東西寫出來,並且寫得盡量讓別人能看懂,最好有喜歡我寫的作品的有緣人。
順便說一下DDD的分布式框架eNode,這是一個非常有專注力的博主,一直堅持分享自己學習DDD的思想結晶,加入他之后感覺自己有了很多提升和感悟,感興趣的可以參與到netfocus的開源項目中去。
netfocus的博客