.net开发笔记(十三) Winform常用开发模式第一篇
? ? ?上一篇博客最后我提到“異步編程模型”(APM),之后本來打算整理一下這方面的材料然后總結(jié)一下寫篇文章與諸位分享,后來在整理的過程中不斷的延伸不斷地?cái)U(kuò)展,發(fā)現(xiàn)完全偏離了“異步編程”這個(gè)概念,前前后后所有的加起來完全可以寫一篇關(guān)于框架原理的東西,而“異步編程”只是其中的一小部分,后來我一狠心,打算把所有的都包含進(jìn)來寫出來,希望給諸位帶來幫助。
? ? ?文章開始之前,先了解幾個(gè)概念:
一、回調(diào)方法。
這個(gè)概念想必都很清楚,被系統(tǒng)調(diào)用的方法就叫做“回調(diào)方法”。是的,描述的沒錯(cuò),通常我們注冊一個(gè)事件,事件處理程序就屬于“回調(diào)方法”。可是不知道諸位有沒有想過,我們在編程過程中,哪些不屬于“回調(diào)方法”呢?有人肯定會(huì)說,我們主動(dòng)調(diào)用的方法就不屬于回調(diào)方法。先不管對不對,我們先來考慮一些問題:系統(tǒng)調(diào)用方法A,那么方法A就是回調(diào)方法,如果我在A中又調(diào)用了方法B,那么B算作回調(diào)方法嗎?再者,什么是所謂的“系統(tǒng)”?指操作系統(tǒng)嗎?亦或是我們編程中使用到的“框架”?最后,我們寫的程序,系統(tǒng)從哪開始調(diào)用?又是在哪結(jié)束?接下來我一一做解答:
1)廣義上講,我們寫的每一行代碼都屬于“回調(diào)代碼”(由代碼組成的方法就叫回調(diào)方法,意思差不多),為什么這么講?因?yàn)槲覀兌贾?#xff0c;任何一個(gè)程序開始運(yùn)行,都是由操作系統(tǒng)調(diào)用某一個(gè)入口方法,那么顯然,這個(gè)入口方法就是理所當(dāng)然的“回調(diào)方法”,進(jìn)入“入口方法”中去之后,就會(huì)執(zhí)行許許多多的其他代碼,也就是說,不管你來回調(diào)用了多少次、嵌套調(diào)用了多少次,我們編寫的所有代碼都間接被操作系統(tǒng)調(diào)用。那么像上面有人可能提到的“主動(dòng)調(diào)用的方法不屬于回調(diào)方法”,其實(shí)在程序中,壓根兒就沒有你能主動(dòng)調(diào)用的方法,如果你寫如下的代碼:
1 private void btn1_Click(object sender,EventArgs e) 2 { 3 Thread th = new Thread((ThreadStart)delegate() 4 { 5 func(); 6 }) 7 th.Start(); 8 } 9 10 void func() 11 { 12 // do something 13 } View Code你可能會(huì)說,func()是我主動(dòng)調(diào)用的,所以它不是回調(diào)方法,但是你要明白,不管你代碼怎么寫,它最終執(zhí)行的主動(dòng)權(quán)不是在你手中,既然主動(dòng)權(quán)不在你手中,那么它就應(yīng)該屬于回調(diào)。
2)這里的“系統(tǒng)”其實(shí)是相對而言的,我前面都把它當(dāng)做操作系統(tǒng),其實(shí)不然,我們口口聲聲說“系統(tǒng)調(diào)用的方法屬于回調(diào)方法”,這里的“系統(tǒng)”絕大多數(shù)是指編程中使用到的“框架”,你比如如下代碼:
1 btn1.Click+=new EventHandler(btn1_Click); 2 private void btn1_Click(object sender,EventArgs e) 3 { 4 //do something 5 } View Code這里的“回調(diào)方法”btn1_Click是由“微軟Winform開發(fā)框架”調(diào)用的,因此“系統(tǒng)”就是指“Winform開發(fā)框架”。1)中講到的“任何一個(gè)程序開始運(yùn)行,都是由操作系統(tǒng)調(diào)用某一個(gè)入口方法”,那么這里的“系統(tǒng)”就是指操作系統(tǒng)。任何都是相對而言的,任何“框架”對于操作系統(tǒng)而言,都可以算得上是“回調(diào)”,而我們寫的所有代碼相對于“框架”而言,也都能算得上“回調(diào)”。
3)我們寫的程序,就用Windows Form應(yīng)用程序?yàn)槔影?#xff0c;對于操作系統(tǒng)而言,第一個(gè)回調(diào)方法應(yīng)該是Main方法,由Main進(jìn)入Application.Run(…),至于何時(shí)結(jié)束,當(dāng)然是Main方法最后的一個(gè)“反花括號”。至于中間使用了哪些“框架”,我們自己又寫了哪些代碼,對于操作系統(tǒng)而言,全部都是一樣的,那都屬于“回調(diào)代碼”。總之,廣義上講,沒有不是“回調(diào)”的代碼(方法)。
圖1
二、泵。
這是個(gè)非常重要的概念,也是本篇文章之后的核心。 生活中提到泵,我們至少可以想到兩點(diǎn):
1)持續(xù)運(yùn)作。也就是說,泵能長時(shí)間循環(huán)工作。
2)傳輸作用。泵能夠?qū)⑺纫后w從一個(gè)地方運(yùn)輸?shù)搅硗庖粋€(gè)地方,供其他人使用。
圖2
以上是生活中見到的泵,那么在編程中,泵是指什么呢?類比一下,其實(shí)很容易想到,程序中的泵具備以下兩個(gè)特點(diǎn):
1)? 循環(huán)執(zhí)行。類似一個(gè)while循環(huán),能夠長時(shí)間循環(huán)工作。
2)? 數(shù)據(jù)傳輸。它能夠?qū)?shù)據(jù)(不再是水等液體)從一個(gè)地方搬遷到另一個(gè)地方,供其他人使用。
程序中的泵,最簡單的利用while就能實(shí)現(xiàn),如下代碼:
1 Queue<Data> container = new Queue<Data>(); //數(shù)據(jù)容器 2 // do something before 3 while(GetData(data)) //從container取數(shù)據(jù) 4 { 5 //實(shí)際中,可以先將data稍作處理 6 SendDataToOtherPlace(data); //將數(shù)據(jù)傳送到其他地方,供其他人使用 7 } 8 //do something … 9 //end 10 11 12 void SendDataToOtherPlace(data) 13 { 14 DealWithData(data); //使用數(shù)據(jù) 15 } View Code? ? 如你所見,以上代碼很好的解釋了編程中的“泵”含義。那么,程序中為啥需要使用泵?原因可以從泵的作用中找,我們很清楚,平時(shí)調(diào)試一些代碼時(shí),可能很多人使用Console程序,如果我們在代碼中不設(shè)置一個(gè)阻塞斷點(diǎn)的話(如Console.Read()),程序執(zhí)行完畢后,黑屏就會(huì)消失,我們看不到任何結(jié)果,如下代碼:
1 void main() 2 { 3 int a =0,b=1; 4 int c = a; 5 a=b; 6 b=c; 7 Console.WriteLine(“a is ”+a+”,b is ” + b); 8 } View Code? ? 其根本原因,是我們寫的程序是“線段”狀,所謂線段,即它是有限長度的直線,線程從main開始,筆直的結(jié)束了。可是我們用的大多數(shù)軟件從來不會(huì)一開始運(yùn)行,馬上就結(jié)束(除非某些特定功能軟件)了,他們絕大多數(shù)都是長時(shí)間持續(xù)運(yùn)行,好了,聽到“長時(shí)間持續(xù)運(yùn)行”,我們就想到了“泵”有這種功能,是的,“泵”不僅僅有這種功能,它還能將數(shù)據(jù)從一個(gè)地方搬遷到另外一個(gè)地方,供其他人使用。到此,程序中使用“泵”是必然。
? ? 講到這里,相信有很多人開始意識到自己在編程中已經(jīng)見過或者使用過“泵”,比如一般界面編程中的“windows消息循環(huán)”,如果有人說沒聽說,它見過你你卻沒見過它,那說明你對Windows桌面開發(fā)還不是很了解,建議看看本系列博客之透過現(xiàn)象看本質(zhì)。反觀Windows操作系統(tǒng),它其實(shí)就是一個(gè)非常大的“泵”,長時(shí)間持續(xù)工作,從“串口”、“鍵盤鼠標(biāo)”、“麥克風(fēng)”、“攝像頭”、“網(wǎng)絡(luò)端口”等等緩沖區(qū)中獲取數(shù)據(jù),傳遞給各種各樣的程序使用。看一張程序中“泵”結(jié)構(gòu)圖:
圖3
實(shí)際編程中,用到“泵”的地方很多,只要某一個(gè)環(huán)節(jié)(跟模塊的意思差不多,只是個(gè)人覺得環(huán)節(jié)更具體,模塊指的范圍太大,下同)需要長時(shí)間持續(xù)工作,同時(shí)不斷存在一系列數(shù)據(jù)需要被處理,那么就可以使用泵。總結(jié)一下,程序中需要使用“泵”的地方有兩個(gè)明顯特點(diǎn):
1)? 該環(huán)節(jié)需要持續(xù)運(yùn)作,也就是需要循環(huán)運(yùn)行,不會(huì)馬上結(jié)束;
2)? 有一些數(shù)據(jù)需要被處理,這些數(shù)據(jù)一般存放在某個(gè)容器中,需要不斷地取出來傳給別人使用。
? ? 前面提到的“Windows消息循環(huán)”就是一個(gè)泵,它符合以上兩個(gè)特點(diǎn),第一,UI線程不可能馬上結(jié)束,需要長時(shí)間持續(xù)運(yùn)作;第二,源源不斷的有Windows消息(一種數(shù)據(jù))需要被處理(數(shù)據(jù)存放在線程的消息隊(duì)列中)。為了更好理解,附圖一張:
圖4
既然“泵”是一種循環(huán),并且每一次循環(huán)執(zhí)行都是需要時(shí)間損耗的,這樣就出現(xiàn)了一個(gè)問題,如果某一次循環(huán)耗時(shí)太長,單次循環(huán)不能立刻返回,那么需要處理的數(shù)據(jù)就會(huì)大量累積,不能及時(shí)取出處理,造成堵塞。這個(gè)問題其實(shí)我們經(jīng)常遇見過(或許又是它天天見到你,你卻沒看見它),我們編程時(shí),有時(shí)候會(huì)遇見界面卡、不流暢、反應(yīng)慢等現(xiàn)象,大部分原因就是因?yàn)?#xff0c;消息處理泵(消息循環(huán))某一次循環(huán)耗時(shí)太長,循環(huán)不能迅速返回,windows消息大量累積,得不到及時(shí)處理,造成界面反應(yīng)遲鈍。
三、線程和方法的關(guān)系。
這個(gè)問題其實(shí)本系列第一篇博客中講到過,線程和方法沒有一對一的關(guān)系,一個(gè)線程可以調(diào)用許多方法,一個(gè)方法也可以運(yùn)行在多個(gè)線程中。前面一句很好理解,后面一句其實(shí)也好理解,看如下代碼:
1 void func() 2 { 3 //do something 4 } 5 Thread th1 = new Thread(new ThreadStart(func)); 6 Thread th2 = new Thread(new ThreadStart(func)); 7 8 Th1.Start(); th2.Start(); //th1 和 th2 執(zhí)行了同一個(gè)方法func View Code如果func中沒有訪問外部變量,基本上不會(huì)出問題,但是如果func中訪問了外部對象,而該對象不是線程安全的,那么你就得在func中做一些“安全措施”了,這點(diǎn)很容易被忽略,如下:
1 List<int> list = new List<int>(); 2 void func() 3 { 4 list.Add(DateTime.Now.Hours); 5 } View Code我們在設(shè)計(jì)func方法的時(shí)候,應(yīng)該考慮該方法將來可能在哪些地方被調(diào)用,如果只在一個(gè)線程中調(diào)用(比如UI線程),那么沒有任何問題,但是如果func有可能運(yùn)行在多個(gè)線程中,那么你就需要做一些“安全措施”了,比如加鎖等。
? ? 總之你在設(shè)計(jì)一個(gè)方法的時(shí)候,務(wù)必要考慮這個(gè)方法將來可能在哪些地方調(diào)用,如果是控件類的成員方法,你更要考慮,因?yàn)榭丶惓蓡T方法一般都會(huì)方法UI,如果這個(gè)成員方法將來被其它線程(非UI線程)調(diào)用,那么就會(huì)出現(xiàn)異常。
以上三個(gè)概念有些本篇文章有用,有些閱讀下一篇我分享一個(gè)UDP通信demo的時(shí)候有用。
正文:
理解以上三個(gè)概念,我認(rèn)為對熟悉接下來要說的有很大幫助。下面,我介紹一個(gè)winform中常用到的開發(fā)模式,該模式就是通過“泵”來實(shí)現(xiàn)的,不敢說諸位平時(shí)用到的所有的框架都是基于這種模式,但我敢說我用到過的框架都是以此為基礎(chǔ)的(下一篇博客,我會(huì)分享一個(gè)UDP通信demo,用具體的實(shí)例來說明該開發(fā)模式)。
據(jù)我開發(fā)經(jīng)驗(yàn),總結(jié)出來4種需要使用到“泵”的場合:
(1)當(dāng)然是之前提到過的有關(guān)“Windows消息循環(huán)”這一塊,它幾乎是所有Windows桌面應(yīng)用程序開發(fā)的精髓。
(2)Socket通信這一塊,包括UDP和TCP兩部分,我之后會(huì)做一個(gè)UDP的Demo。
(3)串口通信這一塊。
(4)麥克風(fēng)、攝像頭數(shù)據(jù)采集這一塊。
大概常用的有這四種,其實(shí)意思都差不多,就是之前我們講到的:都涉及到持續(xù)運(yùn)行,都需要不斷的取數(shù)據(jù)、分配(傳遞)數(shù)據(jù)、別人再處理(使用)數(shù)據(jù)。我具體說一說(1)和(2),弄清楚前兩個(gè),后面兩個(gè)也就清楚明了了。
(1)要了解“Windows消息循環(huán)”,我們先得了解一個(gè)流程:鼠標(biāo)點(diǎn)擊按鈕,鼠標(biāo)驅(qū)動(dòng)采集物理信息,轉(zhuǎn)換成數(shù)字信息,存在一個(gè)緩沖區(qū)A,我們稱該數(shù)字信息為“原始數(shù)據(jù)”(你可以理解為包含鼠標(biāo)XY坐標(biāo)、左右鍵狀態(tài)等等),之所以稱之為“原始數(shù)據(jù)”,是因?yàn)樵摂?shù)據(jù)跟咱們的程序沒有任何關(guān)聯(lián),它只是簡單地包含了鼠標(biāo)當(dāng)前狀態(tài)信息。接下來就有一個(gè)“數(shù)據(jù)采集泵”循環(huán)將這些原始數(shù)據(jù)采集過來,放到另外一個(gè)緩沖區(qū)B,對應(yīng)有一個(gè)“數(shù)據(jù)分析泵”,循環(huán)將緩沖區(qū)B中的原始數(shù)據(jù)取出,分析該“原始數(shù)據(jù)”,參照Windows系統(tǒng)“內(nèi)部數(shù)據(jù)庫”(一種存放窗體、線程等資源的組織),將原始數(shù)據(jù)轉(zhuǎn)換成標(biāo)準(zhǔn)的“Windows消息”(一種數(shù)據(jù)結(jié)構(gòu),包含窗體Handle,類型、參數(shù)等),接著再將轉(zhuǎn)換之后生成的“Windows消息”存放到緩沖區(qū)C(就是我們經(jīng)常聽到的消息隊(duì)列),此時(shí),又有一個(gè)“數(shù)據(jù)處理泵”(就是我們常說的消息循環(huán))循環(huán)取出緩沖區(qū)C中的“Windows消息”,分配該消息給對應(yīng)的窗口過程(WndProc),供其使用(處理),窗口過程就會(huì)激發(fā)Click事件,接著,你的事件處理程序(如btn1_Click)就會(huì)被調(diào)用,至此,整個(gè)過程結(jié)束。上圖一張,更清楚:
圖5
? ? 如我們所見,整個(gè)過程使用了3個(gè)泵,他們互相配合使用,“數(shù)據(jù)采集泵”負(fù)責(zé)將“原始數(shù)據(jù)”從緩沖區(qū)A傳遞到緩沖區(qū)B,“數(shù)據(jù)分析泵”負(fù)責(zé)取出緩沖區(qū)B中的原始數(shù)據(jù),然后進(jìn)行分析,轉(zhuǎn)換成Windows消息(一種程序能夠識別的數(shù)據(jù)結(jié)構(gòu)),進(jìn)而傳遞到緩沖區(qū)C,也就是我們常說到的“消息隊(duì)列”,然后“數(shù)據(jù)處理泵”,我們常說的“消息循環(huán)”,循環(huán)從緩沖區(qū)C中取出消息,分配給對應(yīng)的窗口過程,供其使用。
有人可能會(huì)說,干嘛要分三個(gè)“泵”,一個(gè)“泵”不就能搞定嗎,在“數(shù)據(jù)采集泵”中分析數(shù)據(jù)、轉(zhuǎn)換數(shù)據(jù)、處理數(shù)據(jù)?不能的原因至少有兩個(gè):
以上是“泵”在Windows消息處理中的應(yīng)用。接下來說一下Socket編程中的應(yīng)用,我以UDP通信為例,TCP類似。
(2)我們先理清UDP通信流程:遠(yuǎn)程主機(jī)給本地主機(jī)發(fā)送一個(gè)UDP數(shù)據(jù)包,需要注意的是,在到達(dá)本地主機(jī)之前(傳輸過程中),數(shù)據(jù)包應(yīng)該是一種物理信息,經(jīng)過網(wǎng)卡驅(qū)動(dòng)轉(zhuǎn)換后,物理信息變成數(shù)字信息,存放在緩沖區(qū)A中(一串字節(jié)流,稱之為原始數(shù)據(jù)),此時(shí),需要一個(gè)“數(shù)據(jù)接收泵”循環(huán)取出緩沖區(qū)A中的原始數(shù)據(jù)(UDP中該數(shù)據(jù)應(yīng)該是一個(gè)完整的數(shù)據(jù)包),將其存放到緩沖區(qū)B中,對應(yīng)有一個(gè)“數(shù)據(jù)分析泵”循環(huán)取出緩沖區(qū)B中的原始數(shù)據(jù),根據(jù)事先規(guī)定好的“協(xié)議”(一種通信規(guī)則,通信各方必須同時(shí)遵守),將該原始數(shù)據(jù)解析成程序可識別數(shù)據(jù)(數(shù)據(jù)頭,程序中可識別數(shù)據(jù),遠(yuǎn)程IP端口等),緊接著將解析之后的數(shù)據(jù)存放到緩沖區(qū)C,對應(yīng)又有一個(gè)“數(shù)據(jù)處理泵”循環(huán)從C中取出數(shù)據(jù),分配數(shù)據(jù),通知他人處理。上圖一張:
圖6
現(xiàn)在已經(jīng)很清楚,這個(gè)模式跟“windows消息循環(huán)”是一個(gè)意思,接收數(shù)據(jù)->分析數(shù)據(jù)->處理數(shù)據(jù),每個(gè)環(huán)節(jié)都有一個(gè)“泵”與之關(guān)聯(lián),當(dāng)然還有一個(gè)緩沖區(qū)。其實(shí)再拓展一下,我們會(huì)發(fā)現(xiàn)它們都有輸入,都有分析,都有響應(yīng)
再不說了,說多了都是淚,發(fā)現(xiàn)原來它們都是一樣一樣的。TCP跟UDP差不多,只是服務(wù)端需要有“socket偵聽泵”用來監(jiān)聽socket連入,而且每個(gè)連入的socket都對應(yīng)有自己的“數(shù)據(jù)接收泵”跟“數(shù)據(jù)分析泵”,原因很簡單,因?yàn)門CP按照“流”來傳輸數(shù)據(jù)的,數(shù)據(jù)包之間沒有界限,某一次接收到的“原始數(shù)據(jù)”可能不是一個(gè)完整的包,因此,每個(gè)客戶端socket必須有自己的“數(shù)據(jù)接收泵”和“數(shù)據(jù)分析泵”以及對應(yīng)的緩沖區(qū),并且“數(shù)據(jù)分析泵”中還要具備檢測完整包的功能。TCP版本Demo以后我再做一個(gè),稍微比UDP復(fù)雜一點(diǎn)。
? ? 以上是所有的介紹,理論性的東西非常多,下一篇文章我打算分享一個(gè)UDP通信demo,采用本篇所講內(nèi)容,簡單的實(shí)現(xiàn)了類似飛鴿傳書的功能。
? ??順便帶個(gè)題外話,這一系列文章可能跟實(shí)際具體開發(fā)關(guān)聯(lián)性不是很大,特別像之前說到的“運(yùn)行時(shí)和設(shè)計(jì)時(shí)”、“winform框架原理”等等這些,基本上跟平時(shí)工作沾不上邊,我也沒有刻意去寫平時(shí)工作中遇到的問題,寫出來的東西大都是概念性、原理性偏多一些。各位在看的時(shí)候沒必要跟實(shí)際工作內(nèi)容做比較,全當(dāng)做是一種業(yè)余研究就OK了, O(∩_∩)O~。
轉(zhuǎn)載于:https://www.cnblogs.com/xiaozhi_5638/p/3167794.html
總結(jié)
以上是生活随笔為你收集整理的.net开发笔记(十三) Winform常用开发模式第一篇的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: perl学习之哈希
- 下一篇: EXT扩展实例:在EXT4中检测Ifra