最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

C#實際案例分析(第五彈)——By 流星

2021-12-15 17:39 作者:ForeverMeteor  | 我要投稿

實驗五

題目要求

2.2 模擬QQ聊天程序。具體地,同時打開兩個程序(Q1,Q2),Q1和Q2分別通過一個文件Msg.dat進(jìn)行消息交換,要求:
1)QQ程序分為兩個線程,線程A負(fù)責(zé)用戶的界面顯示,界面輸入; 線程B負(fù)責(zé)監(jiān)控Msg.dat是否有新的消息到達(dá);
2)B監(jiān)控到如果有消息到達(dá),把消息傳遞給線程A;同時,線程B監(jiān)控是否對方正在進(jìn)行鍵盤輸入。如果有,讓A展示“對方正在輸入”的提示;
3)Q1和Q2可以互相傳遞消息;且僅通過Msg.dat這一個文件進(jìn)行信息交換。(可以規(guī)定存儲的策略,方便后繼讀取和寫入)

環(huán)境設(shè)置

操作系統(tǒng): Windows 10 x64

SDK: .NET Framework 4.7.2

IDE: Visual Studio 2019

題意分析

這道題的需求較為復(fù)雜,我們拆開并歸納一下,大概有以下幾個要求:

① 同時打開兩個程序,每個程序有兩個線程
② 兩個程序互相傳遞消息,且只通過Msg.dat寫入或獲取信息
③ A線程接受B線程的信息,并負(fù)責(zé)用戶的界面顯示(接收到對方正在輸入的狀態(tài)且展示,接收到消息到達(dá)且展示),界面輸入(寫消息,發(fā)送消息)
④ B線程監(jiān)控Msg.dat是否有新的消息到達(dá),并負(fù)責(zé)給A線程發(fā)送信息(對方正在輸入,消息到達(dá))

①要求在每個程序運行時擁有兩個不同的線程控制不同的功能,這需要用到C#的多線程功能,我們將在下面詳細(xì)介紹。②要求每個程序之間的信息通信只能靠Msg.dat這一文件,為此我們需要復(fù)習(xí)先前文件讀寫的內(nèi)容。注意,.dat是二進(jìn)制文件,即我們讀和寫的時候應(yīng)以二進(jìn)制的形式打開。對于③和④,我們需要用全局函數(shù)等方式,在線程的while循環(huán)中通過改變參數(shù),進(jìn)而控制他們的功能,在下面我們將詳細(xì)解析。

我們來捋一下本代碼基本的邏輯:首先需要一個父窗體,他下屬有兩個子窗體,每個子窗體都有兩個線程在運行。這兩個線程既要發(fā)送消息,又要接收消息。如果直接在平行關(guān)系的兩個窗口的這么多線程之間直接互相傳遞消息,是非常困難的。于是我們想:能不能借用一個公共平臺,也就是他們的父窗體來進(jìn)行消息之間的傳遞呢?但是光是這樣想,很容易搞出來一大堆全局變量,筆者第一次寫的時候就是這樣。所以,我們需要將必要的函數(shù)封裝到子類里面。這樣一來思路就變得清晰了,他們的邏輯關(guān)系可以歸納如下:

基本邏輯

為了實現(xiàn)我們的代碼,接下來我們先介紹多線程。

多線程

一個程序開始運行時他就是一個進(jìn)程,進(jìn)程包括執(zhí)行中的程序和程序所使用到的內(nèi)存及系統(tǒng)資源。

準(zhǔn)確地來說,一個進(jìn)程就是“一個具有獨立功能的程序關(guān)于某個數(shù)據(jù)集合的一次運行活動?!?/span>。為了簡單理解,我們把它看做一個運行中的程序就行了。

我們往期寫過的程序,在同一時間只能干唯一的一件事情(也就是所謂的單線程)。但是,許多情景下要求我們能夠同時運行多個程序;或者說,同一時間內(nèi)執(zhí)行不同的功能,例如并行計算。于是,多線程應(yīng)運而生:

每一個進(jìn)程是由多個線程組成的。線程是程序中的一個執(zhí)行流。每個線程都有自己的專有寄存器,但代碼區(qū)共享。多線程是指程序中包含多個執(zhí)行流,即在一個程序中可以同時運行多個不同的線程來執(zhí)行不同的任務(wù)。

也就是說,多線程就是一個程序的“下屬”。一個程序可以有好多個這樣的下屬,在同一時間干著不同的事情。多線程的好處在于可以提高CPU的利用率。比如一個線程等待資源時可以運行另外的線程。線程的狀態(tài)有就緒、阻塞、運行等。其中阻塞表示暫時停下該線程的運轉(zhuǎn)直到某一特定時刻。一般使用C#中的Sleep函數(shù)阻塞本線程一段時間,或是使用Join方法等停下當(dāng)前線程,直到某線程運行結(jié)束。

C#為我們提供了許多有關(guān)多線性的類,其中最重要的就是Thread類,其他還有Monitor類,Mutex類等。我們將在接下來的代碼解析中學(xué)習(xí)Thread類的用法。

①:參考文獻(xiàn)2

完整代碼

Form2/父窗體

Form1/子窗體

代碼片段分析

我們先從父窗體(Form2)開始講起:

在類Form2中

注意!Control.CheckForIllegalCrossThreadCalls = false;是非常不好的做法,它是線程不安全的。C#不允許跨線程直接修改非本線程發(fā)起者的控件內(nèi)容,除非加入上面說的那個語句。但是這么做將造成線程不安全的問題,即不同的線程可能修改同一控件,引發(fā)異常。雖然在我們的這次實驗中不會出現(xiàn)這樣的問題(單機運行情況下無法兩窗體同時發(fā)送信息),但是我們?nèi)孕柚勒_的解決方法:使用Invoke函數(shù)進(jìn)行跨線程的調(diào)用。鑒于本代碼的生命周期,本文不再贅述,讀者可自行查閱相關(guān)資料了解。

清空文件的做法則是防止上次運行已存在的文件影響本次運行。當(dāng)然,也可以在此程序運行結(jié)束,釋放資源時將這個文件刪除掉。
②參考網(wǎng)站:https://blog.csdn.net/weixin_38211198/article/details/90708008
https://blog.csdn.net/hongkaihua1987/article/details/7439231

在Form2.button1_Click()中

將創(chuàng)建出來的Q1和Q2的父窗體(Owner)設(shè)置為自己,它和接下來要講的Form1中的這兩行代碼遙相呼應(yīng):

((Form2)Owner).EventComing(Text,?BType.KeyBoardTyping);//Form1.richTextBox1_TextChanged()
((Form2)Owner).EventComing(Text,?BType.MsgReceive);//Form1.button1_Click()

其中Form中的EventComing函數(shù)如下:

是不是有點繞?根據(jù)之前的邏輯。我們需要借助父窗體來實現(xiàn)兩個子窗體的互相通信。設(shè)置好了父窗體之后,我們要通過調(diào)用父窗體的public方法來更改子窗體的變量,實現(xiàn)向子窗體輸出信息的功能。就像下面的這張圖一樣:

而能這么做的前提就是設(shè)置好父窗體。這里注意窗體的Owner調(diào)用出來返回的結(jié)果是Form類,而非父窗體的那個子類(Form2),因而需要強制轉(zhuǎn)換,否則將找不到對應(yīng)的函數(shù)。本程序的邏輯我們已經(jīng)在之前捋清楚了,這些代碼就是基本邏輯的實現(xiàn)。

Form2的代碼解析已經(jīng)結(jié)束了,但是請不要忘記它!它是父窗體,接下來的許多事情都和它有關(guān)系。

在類Form1中

現(xiàn)在可以來解析Thread類的詳細(xì)運用了。一個線程中只會運行一個函數(shù)。創(chuàng)建線程有靜態(tài)方式(適合無參)和委托方式(都可以,適合有參)。這里我們使用委托的方式,即創(chuàng)建一個委托實例,并將它傳到有參線程創(chuàng)建的構(gòu)造函數(shù)ParameterizedThreadStart中,就像下面這樣:

而狀態(tài)區(qū)的枚舉類型表示線程運行中的狀態(tài),根據(jù)這些狀態(tài)去執(zhí)行不同的操作;調(diào)用方字符串表示發(fā)起人是“誰”。他們的作用我們會在接下來看到。我們繼續(xù)往下看:

這段代碼就是本次實驗的精髓了,我們將詳細(xì)地剖析它。

首先令人疑惑是事情就是歐·亨利式while (true)。為什么要死循環(huán)?其原因在于,線程執(zhí)行給他的函數(shù)只能順序執(zhí)行,若不加while(true),先不說其初始狀態(tài)是不是Normal,它也只能完成一次功能,這顯然不是我們想要的。所以我們添加了while循環(huán)讓它反復(fù)執(zhí)行直至整個exe結(jié)束運行或者窗體被關(guān)掉。

其次,根據(jù)之前的邏輯,線程調(diào)用的引起窗體內(nèi)容變化的,都是它被發(fā)起的那個實例中的函數(shù)(我把這一集合叫做本地函數(shù),區(qū)別于其他),而消息的接收則體現(xiàn)在bType的改變。while循環(huán)不停地讀取bType的值,一旦發(fā)現(xiàn)它改變就作出反應(yīng),發(fā)給A線程,剛好符合程序的基本邏輯。而發(fā)起方string的作用也在此體現(xiàn),一旦A線程開始執(zhí)行某一函數(shù),就能獲取發(fā)起方的消息并顯示了。本地函數(shù)都是簡單的修改對象控件的內(nèi)容,這里就不再贅述了。

接下來是多線程里線程狀態(tài)的改變,還記得它都有什么狀態(tài)嗎?

就緒、阻塞、運行……先前的代碼中有A.Start()這一函數(shù),表示線程的啟動;而這個地方的A.Abort()則是“踩下線程的剎車”,試圖讓他停下,即線程的終止。剎車踩下不會馬上停止,而是要等一段時間,線程也一樣。在其他場景下這可能會引發(fā)問題,比如在銷毀過程中再度調(diào)用,故Abort函數(shù)常常配合Jion函數(shù)使用,阻塞線程直至他完全被終止。由于這次實驗中我們只在最后關(guān)閉并釋放資源的時候才銷毀,就不存在這樣的問題了。

總結(jié)

最終程序的運行結(jié)果如下:

最終運行結(jié)果

通過這次實驗,我們學(xué)習(xí)到了C#中多線程的知識。多線程的知識很重要,它是現(xiàn)代軟件制造必須的內(nèi)容,不僅在這次實驗中用到,在將來的軟件開發(fā),Web編程中都將使用得到。本次實驗次重要的地方在于捋清楚父窗體和子窗體、子窗體和子窗體間傳遞消息的組織邏輯,不要凌亂。

但是這個程序仍然存在缺陷。最大的問題在于,委托與事件機制的不夠完善。有許多函數(shù)甚至是全局變量可以通過委托-事件機制,甚至是接口來解決,從而提高封裝的程度,有興趣的讀者可以自己完成。

參考文獻(xiàn)

李春葆,曾平,喻丹丹.C#程序設(shè)計教程(第3版):清華大學(xué)出版社,2015

汪維華,汪維清,胡章平.C#程序設(shè)計實用教程(第2版):清華大學(xué)出版社,2011

Copyright @ 2021, Bilibili: ForeverMeteor, all rights reserved.?

C#實際案例分析(第五彈)——By 流星的評論 (共 條)

分享到微博請遵守國家法律
昌都县| 仁寿县| 麻栗坡县| 江北区| 璧山县| 汉沽区| 丘北县| 正镶白旗| 青浦区| 得荣县| 拜城县| 库伦旗| 浦城县| 元江| 长子县| 桃园县| 木里| 鄂温| 江永县| 儋州市| 灵璧县| 郧西县| 望江县| 滨海县| 宁海县| 磐安县| 朝阳区| 镇雄县| 哈密市| 班戈县| 和田县| 永寿县| 陆川县| 淄博市| 盱眙县| 隆林| 眉山市| 南雄市| 望奎县| 清苑县| 加查县|