电竞比分网-中国电竞赛事及体育赛事平台

分享

C# 溫故而知新: 線(xiàn)程篇(一)

 仰望//45度微笑 2012-09-12

  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)界面:

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多