|
1 線(xiàn)程基礎(chǔ)的簡(jiǎn)單介紹 首先讓我們翻開(kāi)書(shū)本來(lái)了解下線(xiàn)程的一些基礎(chǔ)知識(shí): 1 線(xiàn)程有時(shí)被稱(chēng)為輕量級(jí)進(jìn)程,是程序執(zhí)行流的最小單元 2 線(xiàn)程時(shí)由線(xiàn)程ID,當(dāng)前指令指針(PC),寄存器集合和堆棧組成。 3 線(xiàn)程自身不能擁有系統(tǒng)資源,但是可以使用線(xiàn)程所屬進(jìn)程所占有的系統(tǒng)資源 4 線(xiàn)程可以創(chuàng)建和撤銷(xiāo)另一個(gè)線(xiàn)程 5 線(xiàn)程可以擁有自身的狀態(tài),例如 運(yùn)行狀態(tài),掛起狀態(tài),銷(xiāo)毀釋放狀態(tài)等等 6 線(xiàn)程具有優(yōu)先級(jí),每個(gè)線(xiàn)程都分配了0-31 級(jí)別的其中一個(gè)優(yōu)先級(jí),數(shù)字越大,優(yōu)先級(jí)越高,然而手動(dòng)分配優(yōu)先級(jí)過(guò)于復(fù)雜,所以微軟為我們的Thread類(lèi)提供一個(gè)優(yōu)先級(jí)的枚舉,ThreadPriority枚舉便是優(yōu)先級(jí)枚舉,我們可以利用thread.Priority屬性來(lái)進(jìn)行設(shè)置 7 線(xiàn)程開(kāi)銷(xiāo),這個(gè)是個(gè)復(fù)雜的話(huà)題,希望有機(jī)會(huì)的話(huà)能夠單獨(dú)寫(xiě)一遍文章解釋下 那么多線(xiàn)程有什么實(shí)際好處呢? 首先讓我們了解下多線(xiàn)程的概念:一個(gè)程序或者進(jìn)程中同時(shí)運(yùn)行多個(gè)線(xiàn)程完成不同的工作從概念中我們便可知道多線(xiàn)程的優(yōu)點(diǎn)了 1 能夠?qū)崿F(xiàn)并行操作,也就是說(shuō)多個(gè)線(xiàn)程可以同時(shí)進(jìn)行工作 2 利用多線(xiàn)程后許多復(fù)雜的業(yè)務(wù)或者是計(jì)算可以交給后臺(tái)線(xiàn)程去完成,從而提高整體程序的性能 3 類(lèi)似于第一條利用多線(xiàn)程可以達(dá)到異步的作用(注意,實(shí)現(xiàn)異步的一種方式是多線(xiàn)程) 當(dāng)然多線(xiàn)程也有一定的問(wèn)題需要注意,那就是線(xiàn)程同步問(wèn)題,關(guān)于這個(gè)問(wèn)題我會(huì)今后的文章中詳細(xì)說(shuō)明 2 線(xiàn)程同步與線(xiàn)程異步的簡(jiǎn)單介紹 *1 線(xiàn)程同步 關(guān)于線(xiàn)程同步的概念最簡(jiǎn)單的理解就是同步方法調(diào)用在程序繼續(xù)執(zhí)行之前,需要等待同步方法執(zhí)行完畢返回結(jié)果很有可能多個(gè)線(xiàn)程都會(huì)對(duì)一個(gè)資源進(jìn)行訪(fǎng)問(wèn),從而導(dǎo)致資源被破壞,所以必須采用線(xiàn)程的同步機(jī)制,例如為共享資源加鎖,當(dāng)其中一個(gè)線(xiàn)程占有了鎖之后,其余線(xiàn)程均不能使用共享資源,只有等其釋放鎖之后,接下來(lái)的其中一個(gè)線(xiàn)程會(huì)占有該鎖,本系列會(huì)從Thread類(lèi)開(kāi)始講起,以后多章都會(huì)討論線(xiàn)程同步機(jī)制,例如鎖機(jī)制,臨界區(qū),互斥,信號(hào)量 同步事件等待句柄; 等等 *2 線(xiàn)程異步 線(xiàn)程異步指的是一個(gè)調(diào)用請(qǐng)求發(fā)送給被調(diào)用者,而調(diào)用者不用等待其結(jié)果的返回,一般異步執(zhí)行的任務(wù)都需要比較長(zhǎng)的時(shí)間,所以為了不影響主線(xiàn)程的工作,可以使用多線(xiàn)程或者新開(kāi)辟一個(gè)線(xiàn)程來(lái)實(shí)現(xiàn)異步,同樣,異步和線(xiàn)程池也有著非常緊密的聯(lián)系,這點(diǎn)我會(huì)在今后有關(guān)線(xiàn)程池的文章中詳細(xì)敘述,線(xiàn)程池和異步線(xiàn)程將在第二章中詳細(xì)闡述下 3 前臺(tái)線(xiàn)程與后臺(tái)線(xiàn)程的簡(jiǎn)單介紹 前臺(tái)線(xiàn)程: 諸如我們Console程序的主線(xiàn)程,wpf或者sliverlight的 界面線(xiàn)程等等,都屬于前臺(tái)線(xiàn)程,一旦前臺(tái)線(xiàn)程奔潰或者終止,相應(yīng)的后臺(tái)線(xiàn)程都會(huì)終止,本章中通過(guò)Thread類(lèi)產(chǎn)生的線(xiàn)程默認(rèn)都是前臺(tái)線(xiàn)程,當(dāng)然我們可以設(shè)置Thread的屬性讓該對(duì)象成為后臺(tái)線(xiàn)程,必須注意的是,一旦前臺(tái)線(xiàn)程全部運(yùn)行完畢,應(yīng)用程序的進(jìn)程也會(huì)釋放,但是假設(shè)Console程序中main函數(shù)運(yùn)行完畢,但是其中幾個(gè)前臺(tái)線(xiàn)程還處在運(yùn)行之中,那么這個(gè)Console程序的進(jìn)程是不會(huì)釋放的,仍然處于運(yùn)行之中,直到所有的前臺(tái)線(xiàn)程都釋放為止 后臺(tái)線(xiàn)程: 和前臺(tái)線(xiàn)程唯一的區(qū)別是,后臺(tái)線(xiàn)程更加默默無(wú)聞,甚至后臺(tái)線(xiàn)程因某種情況,釋放銷(xiāo)毀時(shí)不會(huì)影響到進(jìn)程,也就是說(shuō)后臺(tái)線(xiàn)程釋放時(shí) 不會(huì)導(dǎo)致進(jìn)程的釋放 用一個(gè)例子再來(lái)說(shuō)明下前后臺(tái)線(xiàn)程的區(qū)別: 有時(shí)我們打開(kāi)outlook 后接受郵件時(shí),程序會(huì)失去響應(yīng)或被卡住,這時(shí)候我們?nèi)c(diǎn)擊outlook時(shí)系統(tǒng)會(huì)提示 outlook 失去響應(yīng),是否等待或者關(guān)閉,當(dāng)我們點(diǎn)擊關(guān)閉時(shí),其實(shí)在程序中關(guān)于outlook的所有運(yùn)行的前臺(tái)線(xiàn)程被終止,導(dǎo)致了outlook被關(guān)閉了,其進(jìn)程也隨之釋放消失。但是,當(dāng)我們?cè)趏utlook中點(diǎn)擊更新郵件時(shí),后臺(tái)線(xiàn)程會(huì)去收取郵件的工作,我們可以在此期間關(guān)閉 outlook接受新郵件的后臺(tái)線(xiàn)程,而不會(huì)導(dǎo)致整個(gè)outlook的關(guān)閉 4 細(xì)說(shuō)下Thread 最為關(guān)鍵的構(gòu)造函數(shù) 相信大家再看過(guò)前幾章對(duì)于線(xiàn)程的介紹后,對(duì)線(xiàn)程應(yīng)該有一個(gè)溫故的感覺(jué),那么讓我們開(kāi)始對(duì)thread這個(gè)線(xiàn)程類(lèi)進(jìn)行深層次的研究下,首先要啟動(dòng)一個(gè)線(xiàn)程必須將該線(xiàn)程將要做的任務(wù)告訴該線(xiàn)程,否則,線(xiàn)程會(huì)不知道干什么事導(dǎo)致線(xiàn)程無(wú)意義的開(kāi)啟,浪費(fèi)系統(tǒng)資源,果然,Thread類(lèi)的構(gòu)造函數(shù)提供了以下的版本ThreadStart 和 ParameterThreadStart 參數(shù)都是委托,所以可以看出委托其實(shí)就是方法的抽象,前者用于不帶參數(shù)的并且無(wú)返回值的方法的抽象,后者是帶object參數(shù)的方法的抽象,大家通過(guò)以下簡(jiǎn)單的方法注意下線(xiàn)程如何調(diào)用帶參數(shù)的方法: public class ThreadStartTest { //無(wú)參數(shù)的構(gòu)造函數(shù) Thread thread = new Thread(new ThreadStart(ThreadMethod)); //帶有object參數(shù)的構(gòu)造函數(shù) Thread thread2 = new Thread(new ParameterizedThreadStart(ThreadMethodWithPara)); public ThreadStartTest() { //啟動(dòng)線(xiàn)程1 thread.Start(); //啟動(dòng)線(xiàn)程2 thread2.Start(new Parameter { paraName="Test" }); } static void ThreadMethod() { //.... } static void ThreadMethodWithPara(object o) { if (o is Parameter) { // (o as Parameter).paraName............. } } } public class Parameter { public string paraName { get; set; } } 不帶參數(shù)的方法似乎很簡(jiǎn)單的能被調(diào)用,只要通過(guò)第一個(gè)構(gòu)造函數(shù)便行,對(duì)于帶參數(shù)的方法,大家注意下參數(shù)是如何傳入線(xiàn)程所調(diào)用的方法,當(dāng)啟動(dòng)線(xiàn)程時(shí),參數(shù)通過(guò)thread.Start方法傳入,于是我們便成功啟動(dòng)了thread線(xiàn)程,大伙可千萬(wàn)不要小看基礎(chǔ)啊,往往在復(fù)雜的項(xiàng)目中很多 就是因?yàn)橐恍┗A(chǔ)導(dǎo)致,所以一定不要忽視它。。。 5 細(xì)說(shuō)下Thread 的 Sleep方法 話(huà)說(shuō)微軟對(duì)Thread.Sleep方法的解釋過(guò)于簡(jiǎn)單,導(dǎo)致許多人會(huì)誤認(rèn)為這個(gè)方法并不重要,其實(shí)這是錯(cuò)誤的,其實(shí)線(xiàn)程是非常復(fù)雜的, 而且我們圍繞這個(gè)方法來(lái)溫故下windows系統(tǒng)對(duì)于CPU競(jìng)爭(zhēng)的策略: 所謂搶占式操作系統(tǒng),就是說(shuō)如果一個(gè)進(jìn)程得到了 CPU 時(shí)間,除非它自己放棄使用 CPU ,否則將完全霸占 CPU 。因此可以看出, 在搶占式操作系統(tǒng)中,操作系統(tǒng)假設(shè)所有的進(jìn)程都是“人品很好”的,會(huì)主動(dòng)退出 CPU 。 發(fā)現(xiàn)寫(xiě)到這里貌似真的已經(jīng)比較復(fù)雜了,由于本人對(duì)操作系統(tǒng)底層的知識(shí)比較匱乏,決定還是引用下別人的理解,順便自己也學(xué)習(xí)下 引用: 假設(shè)有源源不斷的蛋糕(源源不斷的時(shí)間),一副刀叉(一個(gè)CPU),10個(gè)等待吃蛋糕的人(10 個(gè)進(jìn)程)。如果是 Unix 操作系統(tǒng)來(lái)負(fù)責(zé)分蛋糕, 那么他會(huì)這樣定規(guī)矩:每個(gè)人上來(lái)吃 1 分鐘,時(shí)間到了換下一個(gè)。最后一個(gè)人吃完了就再?gòu)念^開(kāi)始。于是,不管這10個(gè)人是不是優(yōu)先級(jí)不同、饑餓程度不同、飯量不同,每個(gè)人上來(lái)的時(shí)候都可以吃 1 分鐘。當(dāng)然,如果有人本來(lái)不太餓,或者飯量小,吃了30秒鐘之后就吃飽了,那么他可以跟操 作系統(tǒng)說(shuō):我已經(jīng)吃飽了(掛起)。于是操作系統(tǒng)就會(huì)讓下一個(gè)人接 著來(lái)。如果是 Windows 操作系統(tǒng)來(lái)負(fù)責(zé)分蛋糕的,那么場(chǎng)面就很有意思了。 他會(huì)這樣定規(guī)矩:我會(huì)根據(jù)你們的優(yōu)先級(jí)、饑餓程度去給你們每個(gè)人計(jì)算一個(gè)優(yōu)先級(jí)。優(yōu)先級(jí)最高的那個(gè)人,可 以上來(lái)吃蛋糕——吃到你不想吃為止。 等這個(gè)人吃完了,我再重新根據(jù)優(yōu)先級(jí)、饑餓程度來(lái)計(jì)算每個(gè)人的優(yōu)先級(jí),然后再分給優(yōu)先級(jí)最高的那個(gè)人。這樣看來(lái),這個(gè) 場(chǎng)面就有意思了—— 可能有些人是PPMM,因此具有高優(yōu)先級(jí),于是她就可以經(jīng)常來(lái)吃蛋糕??赡芰硗庖粋€(gè)人的優(yōu)先級(jí)特別低,于是好半天了才輪到他一次(因?yàn)?隨著時(shí)間的推移,他會(huì)越來(lái)越饑餓,因此算出來(lái)的總優(yōu)先級(jí)就會(huì)越來(lái)越高,因此總有一天會(huì)輪到他的)。而且,如果一不小心讓一個(gè)大胖子得到了刀叉,因?yàn)樗埩?大,可能他會(huì)霸占著蛋糕連續(xù)吃很久很久,導(dǎo)致旁邊的人在那里咽口水。。。而且,還可能會(huì)有這種情況出現(xiàn):操作系統(tǒng)現(xiàn)在計(jì)算出來(lái)的結(jié)果,是5號(hào)PPMM總優(yōu) 先級(jí)最高——高出別人一大截。因此就叫5號(hào)來(lái)吃蛋糕。5號(hào)吃了一小會(huì)兒,覺(jué)得沒(méi)那么餓了,于是說(shuō)“我不吃了”(掛起)。因此操作系統(tǒng)就會(huì)重新計(jì)算所有人的 優(yōu)先級(jí)。因?yàn)?號(hào)剛剛吃過(guò),因此她的饑餓程度變小了,于是總優(yōu)先級(jí)變小了;而其他人因?yàn)槎嗟攘艘粫?huì)兒,饑餓程度都變大了,所以總優(yōu)先級(jí)也變大了。不過(guò)這時(shí) 候仍然有可能5號(hào)的優(yōu)先級(jí)比別的都高,只不過(guò)現(xiàn)在只比其他的高一點(diǎn)點(diǎn)——但她仍然是總優(yōu)先級(jí)最高的啊。因此操作系統(tǒng)就會(huì)說(shuō):5號(hào)mm上來(lái)吃蛋糕……(5號(hào) mm心里郁悶,這不剛吃過(guò)嘛……人家要減肥……誰(shuí)叫你長(zhǎng)那么漂亮,獲得了那么高的優(yōu)先級(jí))。那么,Thread.Sleep 函數(shù)是干嗎的呢?還用剛才的分蛋糕的場(chǎng)景來(lái)描述。上面的場(chǎng)景里面,5號(hào)MM在吃了一次蛋糕之后,覺(jué)得已經(jīng)有8分飽了,她覺(jué)得在未來(lái)的半個(gè)小時(shí)之內(nèi)都不想再 來(lái)吃蛋糕了,那么她就會(huì)跟操作系統(tǒng)說(shuō):在未來(lái)的半個(gè)小時(shí)之內(nèi)不要再叫我上來(lái)吃蛋糕了。這樣,操作系統(tǒng)在隨后的半個(gè)小時(shí)里面重新計(jì)算所有人總優(yōu)先級(jí)的時(shí)候, 就會(huì)忽略5號(hào)mm。Sleep函數(shù)就是干這事的,他告訴操作系統(tǒng)“在未來(lái)的多少毫秒內(nèi)我不參與CPU競(jìng)爭(zhēng)”。 6 細(xì)說(shuō)下Thread 的 join 方法 為什么我要把Thread.Join()方法單獨(dú)細(xì)說(shuō)下,個(gè)人認(rèn)為join方法非常重要,在細(xì)說(shuō)前我想再次強(qiáng)調(diào)下主線(xiàn)程和子線(xiàn)程的區(qū)別:首先大家肯定知道在Console程序中,主線(xiàn)程自上而下著運(yùn)行著main函數(shù),假如我們?cè)趍ain函數(shù)中新增一個(gè)線(xiàn)程thread對(duì)象的話(huà), 也就是說(shuō),在主線(xiàn)程中再開(kāi)啟一個(gè)子線(xiàn)程,同時(shí)子線(xiàn)程和主線(xiàn)程可以同時(shí)工作(前提是子線(xiàn)程使用Start方法),同理,假如我在這個(gè)子線(xiàn)程中再開(kāi)辟一個(gè)屬于這個(gè)子線(xiàn)程的子線(xiàn)程,同理這3個(gè)爺爺,父親,兒子線(xiàn)程也可以使用Start()方法一起工作,假如在主線(xiàn) 程中添加2個(gè)thread對(duì)象并開(kāi)啟,那么這2 線(xiàn)程便屬于同一層次的線(xiàn)程(兄弟線(xiàn)程)(和優(yōu)先級(jí)無(wú)關(guān),只同一位置層次上的兄弟),有可能上述的讓你覺(jué)得郁悶或者難以理解?沒(méi)關(guān)系看簡(jiǎn)單例子就能夠理解了 public static void ShowFatherAndSonThread(Thread grandFatherThread) { Console.WriteLine("爺爺主線(xiàn)程名:{0}", grandFatherThread.Name); Thread brotherThread = new Thread(new ThreadStart(() => { Console.WriteLine("兄弟線(xiàn)程名:{0}", Thread.CurrentThread.Name); })); Thread fatherThread = new Thread(new ThreadStart( () => { Console.WriteLine("父親線(xiàn)程名:{0}", Thread.CurrentThread.Name); Thread sonThread = new Thread(new ThreadStart(() => { Console.WriteLine("兒子線(xiàn)程名:{0}", Thread.CurrentThread.Name); })); sonThread.Name = "SonThread"; sonThread.Start(); } )); fatherThread.Name = "FatherThread"; brotherThread.Name="BrotherThread"; fatherThread.Start(); brotherThread.Start(); } 言歸正傳讓我們溫故下Jion方法,先看msdn中是怎么解釋的: 繼續(xù)執(zhí)行標(biāo)準(zhǔn)的 COM 和 SendMessage 消息泵處理期間,阻塞調(diào)用線(xiàn)程,直到某個(gè)線(xiàn)程終止為止。 大家把注意力移到后面紅色的部分,什么是“調(diào)用線(xiàn)程”呢?如果你理解上述線(xiàn)程關(guān)系的話(huà),可能已經(jīng)理解了,主線(xiàn)程(爺爺輩)的調(diào)用了父親線(xiàn)程,父親線(xiàn)程調(diào)用了兒子線(xiàn)程,假設(shè)現(xiàn)在我們有一個(gè)奇怪的需求,必須開(kāi)啟爺爺輩和父親輩的線(xiàn)程但是,爺爺輩線(xiàn)程必須等待父親線(xiàn)程結(jié)束后再進(jìn)行,這該怎么辦? 這時(shí)候Join方法上場(chǎng)了,我們的目標(biāo)是阻塞爺爺線(xiàn)程,那么后面的工作就明確了,讓父親線(xiàn)程(thread)對(duì)象去調(diào)用join方法就行一下是個(gè)很簡(jiǎn)單的例子,讓大家再深入理解下。 public static void ThreadJoin() { Console.WriteLine("我是爺爺輩線(xiàn)程,子線(xiàn)程馬上要來(lái)工作了我得準(zhǔn)備下讓個(gè)位給他。"); Thread t1 = new Thread( new ThreadStart ( () => { for (int i = 0; i < 10; i++) { if (i == 0) Console.WriteLine("我是父親線(xiàn)層{0}, 完成計(jì)數(shù)任務(wù)后我會(huì)把工作權(quán)交換給主線(xiàn)程", Thread.CurrentThread.Name); else { Console.WriteLine("我是父親線(xiàn)層{0}, 計(jì)數(shù)值:{1}", Thread.CurrentThread.Name, i); } Thread.Sleep(1000); } } ) ); t1.Name = "線(xiàn)程1"; t1.Start(); //調(diào)用join后調(diào)用線(xiàn)程被阻塞 t1.Join(); Console.WriteLine("終于輪到爺爺輩主線(xiàn)程干活了"); } 代碼中當(dāng)父親線(xiàn)程啟動(dòng)后會(huì)立即進(jìn)入Jion方法,這時(shí)候調(diào)用該線(xiàn)程爺爺輩線(xiàn)程被阻塞,直到父親線(xiàn)程中的方法執(zhí)行完畢為止,最后父親線(xiàn)程將控制權(quán)再次還給爺爺輩線(xiàn)程,輸出最后的語(yǔ)句。聰明的你肯定會(huì)問(wèn):兄弟線(xiàn)程怎么保證先后順序呢?很明顯如果不使用join,一并開(kāi)啟兄弟線(xiàn)程后結(jié)果是隨機(jī)的不可預(yù)測(cè)的(暫時(shí)不考慮線(xiàn)程優(yōu)先級(jí)),但是我們不能在兄弟線(xiàn)程全都開(kāi)啟后使用join,這樣阻塞了父親線(xiàn)程,而對(duì)兄弟線(xiàn)程是無(wú)效的,其實(shí)我們可以變通一下,看以下一個(gè)很簡(jiǎn)單的例子: public static void ThreadJoin2() { IList<Thread> threads = new List<Thread>(); for (int i = 0; i < 3; i++) { Thread t = new Thread( new ThreadStart( () => { for (int j = 0; j < 10; j++) { if (j == 0) Console.WriteLine("我是線(xiàn)層{0}, 完成計(jì)數(shù)任務(wù)后我會(huì)把工作權(quán)交換給其他線(xiàn)程", Thread.CurrentThread.Name); else { Console.WriteLine("我是線(xiàn)層{0}, 計(jì)數(shù)值:{1}", Thread.CurrentThread.Name, j); } Thread.Sleep(1000); } })); t.Name = "線(xiàn)程" + i; //將線(xiàn)程加入集合 threads.Add(t); } foreach (var thread in threads) { thread.Start(); //每次按次序阻塞調(diào)用次方法的線(xiàn)程 thread.Join(); } } 輸出結(jié)果: 但是這樣我們即便能達(dá)到這種效果,也會(huì)發(fā)現(xiàn)其中存在著不少缺陷: 1:必須要指定順序 2:一旦一個(gè)運(yùn)行了很久,后續(xù)的線(xiàn)程會(huì)一直等待很久 3: 很容易產(chǎn)生死鎖 從前面2個(gè)例子能夠看出 jion是利用阻塞調(diào)用線(xiàn)程的方式進(jìn)行工作,我們可以根據(jù)需求的需要而靈活改變線(xiàn)程的運(yùn)行順序,但是在復(fù)雜的項(xiàng)目或業(yè)務(wù)中對(duì)于jion方法的調(diào)試和糾錯(cuò)也是比較困難的。 7 細(xì)說(shuō)下Thread 的 Abort和 Interrupt方法 Abort 方法: 其實(shí) Abort 方法并沒(méi)有像字面上的那么簡(jiǎn)單,釋放并終止調(diào)用線(xiàn)程,其實(shí)當(dāng)一個(gè)線(xiàn)程調(diào)用 Abort方法時(shí),會(huì)在調(diào)用此方法的線(xiàn)程上引發(fā)一個(gè)異常: ThreadAbortException ,讓我們一步步深入下對(duì)這個(gè)方法的理解: 1 首先我們嘗試對(duì)主線(xiàn)程終止釋放 static void Main(string[] args) { try { Thread.CurrentThread.Abort(); } catch { //Thread.ResetAbort(); Console.WriteLine("主線(xiàn)程接受到被釋放銷(xiāo)毀的信號(hào)"); Console.WriteLine( "主線(xiàn)程的狀態(tài):{0}",Thread.CurrentThread.ThreadState); } finally { Console.WriteLine("主線(xiàn)程最終被被釋放銷(xiāo)毀"); Console.WriteLine("主線(xiàn)程的狀態(tài):{0}", Thread.CurrentThread.ThreadState); Console.ReadKey(); } } 從運(yùn)行結(jié)果上看很容易看出當(dāng)主線(xiàn)程被終止時(shí)其實(shí)報(bào)出了一個(gè)ThreadAbortException, 從中我們可以進(jìn)行捕獲,但是注意的是,主線(xiàn)程直到finally語(yǔ)句塊執(zhí)行完畢之后才真正結(jié)束(可以仔細(xì)看下主線(xiàn)程的狀態(tài)一直處于AbortRequest),如果你在finally語(yǔ)句塊中執(zhí)行很復(fù)雜的邏輯或者計(jì)算的話(huà),那么只有等待直到運(yùn)行完畢才真正銷(xiāo)毀主線(xiàn)程(也就是說(shuō)主線(xiàn)程的狀態(tài)會(huì)變成Aborted,但是由于是主線(xiàn)程所以無(wú)法看出). 2 嘗試終止一個(gè)子線(xiàn)程 同樣先看下代碼: static void TestAbort() { try { Thread.Sleep(10000); } catch { Console.WriteLine("線(xiàn)程{0}接受到被釋放銷(xiāo)毀的信號(hào)",Thread.CurrentThread.Name); Console.WriteLine("捕獲到異常時(shí)線(xiàn)程{0}主線(xiàn)程的狀態(tài):{1}", Thread.CurrentThread.Name,Thread.CurrentThread.ThreadState); } finally { Console.WriteLine("進(jìn)入finally語(yǔ)句塊后線(xiàn)程{0}主線(xiàn)程的狀態(tài):{1}", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState); } } Main: static void Main(string[] args) { Thread thread1 = new Thread(TestAbort); thread1.Name = "Thread1"; thread1.Start(); Thread.Sleep(1000); thread1.Abort(); thread1.Join(); Console.WriteLine("finally語(yǔ)句塊后,線(xiàn)程{0}主線(xiàn)程的狀態(tài):{1}", thread1.Name, thread1.ThreadState); Console.ReadKey(); } 了解了主線(xiàn)程的銷(xiāo)毀釋放后,再來(lái)看下子線(xiàn)程的銷(xiāo)毀釋放的過(guò)程(Start->abortRequested->Aborted->Stop),從最后輸出的狀態(tài)變化來(lái)看,子線(xiàn)程thread1 的狀態(tài)變化是十分清楚的,幾乎和主線(xiàn)程的例子一致,唯一的區(qū)別是我們?cè)?main方法中故意讓主線(xiàn)程阻塞這樣能看見(jiàn)thread 1在 finally語(yǔ)句塊后的狀態(tài) 3,嘗試對(duì)尚未啟動(dòng)的線(xiàn)程調(diào)用Abort 如果對(duì)一個(gè)尚未啟動(dòng)的線(xiàn)程調(diào)用Abort的話(huà),一旦該線(xiàn)程啟動(dòng)就被停止了 4 嘗試對(duì)一個(gè)掛起的線(xiàn)程調(diào)用Abort 如果在已掛起的線(xiàn)程上調(diào)用 Abort,則將在調(diào)用 Abort 的線(xiàn)程中引發(fā) ThreadStateException,并將 AbortRequested 添加到被中止的線(xiàn)程的ThreadState 屬性中。直到調(diào)用 Resume 后,才在掛起的線(xiàn)程中引發(fā) ThreadAbortException。如果在正在執(zhí)行非托管代碼的托管線(xiàn)程上調(diào)用 Abort,則直到線(xiàn)程返回到托管代碼才引發(fā) ThreadAbortException。 Interrupt 方法: Interrupt 方法將當(dāng)前的調(diào)用該方法的線(xiàn)程處于掛起狀態(tài),同樣在調(diào)用此方法的線(xiàn)程上引發(fā)一個(gè)異常:ThreadInterruptedException,和Abort方法不同的是,被掛起的線(xiàn)程可以喚醒 static void Main(string[] args) { Thread thread1 = new Thread(TestInterrupt); thread1.Name = "Thread1"; thread1.Start(); Thread.Sleep(1000); thread1.Interrupt(); thread1.Join(); Console.WriteLine("finally語(yǔ)句塊后,線(xiàn)程{0}主線(xiàn)程的狀態(tài):{1}", thread1.Name, thread1.ThreadState); Console.ReadKey(); } static void TestInterrupt() { try { Thread.Sleep(3000); } catch (ThreadInterruptedException e) { Console.WriteLine("線(xiàn)程{0}接受到被Interrupt的信號(hào)", Thread.CurrentThread.Name); Console.WriteLine("捕獲到Interrupt異常時(shí)線(xiàn)程{0}的狀態(tài):{1}", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState); } finally { Console.WriteLine("進(jìn)入finally語(yǔ)句塊后線(xiàn)程{0}的狀態(tài):{1}", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState); } } 從代碼中可以看出,當(dāng)線(xiàn)程調(diào)用Interrupted后,它的狀態(tài)是已中斷的.這個(gè)狀態(tài)對(duì)于正在執(zhí)行join,sleep的線(xiàn)程,卻改變了線(xiàn)程的運(yùn)行結(jié)果.因?yàn)樗谀骋粚?duì)象的休息室中,這時(shí)如果它的中斷狀態(tài)被改變,那么它就會(huì)拋出ThreadInterruptedException異常,意思就是這個(gè)線(xiàn)程不能再等待了,其意義就等同于喚醒它了。讓我們想象一下我們將一個(gè)線(xiàn)程設(shè)置了其長(zhǎng)達(dá)1星期的睡眠時(shí)間,有時(shí)后必須喚醒它,上述方法就能實(shí)現(xiàn)這點(diǎn) 8 細(xì)說(shuō)下Thread 的 Suspend,Resume方法 Suspend 和Resume方法很奧妙,前者將當(dāng)前運(yùn)行的線(xiàn)程掛起,后者能夠恢復(fù)當(dāng)錢(qián)被掛起的線(xiàn)程 Thread thread1 = new Thread(TestSuspend); Thread thread2 = new Thread(TestSuspend); thread1.Name = "Thread1"; thread2.Name = "Thread2"; thread1.Start(); thread2.Start(); //假設(shè)在做一些事情 Thread.Sleep(1000); Console.WriteLine("需要主線(xiàn)程幫忙了"); // throw new NullReferenceException("error!"); thread1.Resume(); thread2.Resume(); static void TestSuspend() { Console.WriteLine("Thread:{0} has been suspend!",Thread.CurrentThread.Name); //這里講當(dāng)前線(xiàn)程掛起 Thread.CurrentThread.Suspend(); Console.WriteLine("{0} has been resume", Thread.CurrentThread.Name); } 如上代碼,我們制造兩個(gè)線(xiàn)程來(lái)實(shí)現(xiàn)Suspend和Resume的測(cè)試,(暫時(shí)不考慮臨界區(qū)共享同步的問(wèn)題),TestSuspend方法便是兩個(gè)線(xiàn)程的共用方法,方法中我們獲取當(dāng)前運(yùn)行該方法的線(xiàn)程,然后將其掛起操作,那么假設(shè)線(xiàn)程1先掛起了,線(xiàn)程1被中止當(dāng)前的工作,面壁思過(guò)去了,可是這并不影響線(xiàn)程2的工作,于是線(xiàn)程2也急匆匆的闖了進(jìn)來(lái),結(jié)果和線(xiàn)程1一樣的悲劇,聰明的你肯定會(huì)問(wèn),誰(shuí)能讓線(xiàn)程1和線(xiàn)程2恢復(fù)工作?其實(shí)有很多方法能讓他們恢復(fù)工作,但是個(gè)人認(rèn)為,在不創(chuàng)建新線(xiàn)程的條件下,被我們忽視的主線(xiàn)程做不住了,看到自己的兄弟面壁,心里肯定不好受,于是做完他自己的一系列事情之后,他便去召喚這2個(gè)兄弟回來(lái)工作了,可是也許會(huì)有這種情況,主線(xiàn)程迫于自己的事情太多太雜而甚至報(bào)出了異常, 那么完蛋了,這兩個(gè)線(xiàn)程永遠(yuǎn)無(wú)法繼續(xù)干活了,或者直接被回收。。。 這樣這次把他們共享區(qū)上鎖,上面部分的代碼保持不變,這樣會(huì)發(fā)生什么情況呢? static void TestSuspend() { lock (lockObj) { ... } } ?。ㄓ捎谠赥estSuspend方法中加入了鎖,所以每次只允許一個(gè)線(xiàn)程工作,大伙不必在本文中深究鎖機(jī)制,后續(xù)章節(jié)會(huì)給大家詳細(xì)溫故下) 盡然在thread2.resume()方法上報(bào)錯(cuò)了,仔細(xì)分析后發(fā)現(xiàn)在thread1離開(kāi)共享區(qū)(testSuspend)方法之后剎那間,thread2進(jìn)來(lái)了,與此同時(shí),主線(xiàn)程跑的太快了,導(dǎo)致thread2被掛起前去喚醒thread2,悲劇就這么發(fā)生了,其實(shí)修改這個(gè)bug很容易,只要判斷下線(xiàn)程的狀態(tài),或者主線(xiàn)程中加一個(gè)Thread.Sleep()等等,但是這種錯(cuò)誤非常的嚴(yán)重,往往在很復(fù)雜的業(yè)務(wù)里讓你發(fā)狂,所以微軟決定放棄這兩個(gè)方法,將他們歸為過(guò)時(shí)方法,最后讓大家看下微軟那個(gè)深?yuàn)W的解釋?zhuān)嘈趴赐晟鲜隼雍蟠蠹叶寄芾斫膺@個(gè)含義了 9 簡(jiǎn)單了解下Thread 的 一些常用的重要屬性 1 CurrentThread 獲取到當(dāng)前線(xiàn)程的對(duì)象 2 IsAlive 判斷線(xiàn)程是否處于激活狀態(tài) 3 IsBackground 設(shè)置該線(xiàn)程是否是后臺(tái)線(xiàn)程,一旦設(shè)置true 的話(huà),該線(xiàn)程就被標(biāo)示為后臺(tái)線(xiàn)程再次強(qiáng)調(diào)下后臺(tái)線(xiàn)程的終止不會(huì)導(dǎo)致進(jìn)程的終止 4 IsThreadPoolThread 只讀屬性標(biāo)示該線(xiàn)程是否屬于線(xiàn)程池的托管線(xiàn)程,一般我通過(guò)線(xiàn)程池創(chuàng)建的線(xiàn)程該屬性都是true 5 Name 獲取到線(xiàn)程的名字,我們可以根據(jù)業(yè)務(wù)或者邏輯來(lái)自定義線(xiàn)程的名字 6 Priority 這個(gè)屬性表示線(xiàn)程的優(yōu)先級(jí),我們可以用ThreadPriority這個(gè)枚舉來(lái)設(shè)置這個(gè)屬性ThreadPriority包含有5個(gè)優(yōu)先級(jí)大家了解下就行 10 Thread的簡(jiǎn)單示例 在WPF中實(shí)現(xiàn)多線(xiàn)程從一個(gè)圖片中截取部分圖片 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Drawing; using System.Windows.Interop; using System.Threading; namespace ImageFlip { /// <summary> /// WPF 多線(xiàn)程將圖片分割 /// </summary> public partial class MainWindow : Window { BitmapSource source; private object lockObj = new object(); public MainWindow() { InitializeComponent(); //首先獲取圖片 Bitmap orginalImage = new Bitmap(@"G:\Picture\Tamriel_4E.png"); //創(chuàng)建線(xiàn)程1 Thread t1 = new Thread(new ParameterizedThreadStart ( obj => { //WPF中使用多線(xiàn)程的話(huà)最后一定要返回UI線(xiàn)程,否則操作界面控件時(shí)會(huì)報(bào)錯(cuò) //BeginInvoke方法便是返回UI線(xiàn)程的方法 this.Dispatcher.BeginInvoke((Action)(() => { //通過(guò)Parameter類(lèi)的屬性裁剪圖片 ClipImageAndBind(obj); //圖片的部分綁定到頁(yè)面控件 this.TestImage1.Source = source; })); } )); //創(chuàng)建線(xiàn)程2 Thread t2 = new Thread(new ParameterizedThreadStart ( obj => { //WPF中使用多線(xiàn)程的話(huà)最后一定要返回UI線(xiàn)程,否則操作界面控件時(shí)會(huì)報(bào)錯(cuò) //BeginInvoke方法便是返回UI線(xiàn)程的方法 this.Dispatcher.BeginInvoke((Action)(() => { //通過(guò)Parameter類(lèi)的屬性裁剪圖片 ClipImageAndBind(obj); //圖片的部分綁定到頁(yè)面控件 this.TestImage2.Source = source; //嘗試將線(xiàn)程1的啟動(dòng)邏輯放在線(xiàn)程2所持有的方法中 // t1.Start(new Parameter { OrginalImage = orginalImage, ClipHeight = 500, ClipWidth = 500, StartX = 0, StartY = 0 }); })); } )); t2.Start(new Parameter { OrginalImage = orginalImage, ClipHeight = 500, ClipWidth = 500, StartX = orginalImage.Width - 500, StartY = orginalImage.Height - 500 }); //嘗試下注釋掉t2.join方法后是什么情況,其實(shí)注釋掉之后,兩個(gè)線(xiàn)程會(huì)一起工作, //去掉注釋后,界面一直到兩個(gè)圖片部分都綁定完成后才出現(xiàn) //t2.Join(); t1.Start(new Parameter { OrginalImage = orginalImage, ClipHeight = 500, ClipWidth = 500, StartX = 0, StartY = 0 }); } /// <summary> /// 根據(jù)參數(shù)類(lèi)進(jìn)行剪裁圖片,加鎖防止共享資源被破壞 /// </summary> /// <param name="para">Parameter類(lèi)對(duì)象</param> private void ClipImageAndBind(object para) { lock (lockObj) { Parameter paraObject = (para as Parameter); source = this.ClipPartOfImage(paraObject); Thread.Sleep(5000); } } /// <summary> /// 具體裁剪圖片,大家不必在意這個(gè)方法,關(guān)鍵是線(xiàn)程的使用 /// </summary> /// <param name="para">Parameter</param> /// <returns>部分圖片</returns> private BitmapSource ClipPartOfImage(Parameter para) { if (para == null) { throw new NullReferenceException("para 不能為空"); } if (para.OrginalImage == null) { throw new NullReferenceException("OrginalImage 不能為空"); } System.Drawing.Rectangle rect = new System.Drawing.Rectangle(para.StartX, para.StartY, para.ClipWidth, para.ClipHeight); var bitmap2 = para.OrginalImage.Clone(rect, para.OrginalImage.PixelFormat) as Bitmap; return ChangeBitmapToBitmapSource(bitmap2); } private BitmapSource ChangeBitmapToBitmapSource(Bitmap bmp) { BitmapSource returnSource; try { returnSource = Imaging.CreateBitmapSourceFromHBitmap(bmp.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); } catch { returnSource = null; } return returnSource; } } /// <summary> /// 參數(shù)類(lèi) /// </summary> public class Parameter { public Bitmap OrginalImage { get; set; } public int StartX { get; set; } public int StartY { get; set; } public int ClipWidth { get; set; } public int ClipHeight { get; set; } } } 前臺(tái)界面: |
|
|
來(lái)自: 仰望//45度微笑 > 《開(kāi)發(fā)語(yǔ)言》