浅析C#中构建多线程应用程序
***************************************************
更多精彩,歡迎進(jìn)入:http://shop115376623.taobao.com
***************************************************
引言
1.理解多線程
2.?線程異步與線程同步
3.創(chuàng)建多線程應(yīng)用程序
3.1通過(guò)System.Threading命名空間的類構(gòu)建
3.1.1異步調(diào)用線程
3.1.2并發(fā)問(wèn)題
3.1.3線程同步
3.2通過(guò)委托構(gòu)建多線程應(yīng)用程序
3.2.1線程異步
3.2.2線程同步
3.3BackgroundWorker組件
4.總結(jié)
引言
?隨著雙核、四核等多核處理器的推廣,多核處理器或超線程單核處理器的計(jì)算機(jī)已很常見,基于多核處理的編程技術(shù)也開始受到程序員們普遍關(guān)注。這其中一個(gè)重要的方面就是構(gòu)建多線程應(yīng)用程序(因?yàn)椴皇褂枚嗑€程的話,開發(fā)人員就不能充分發(fā)揮多核計(jì)算機(jī)的強(qiáng)大性能)。
?本文針對(duì)的是構(gòu)建基于單核計(jì)算機(jī)的多線程應(yīng)用程序,目的在于介紹多線程相關(guān)的基本概念、內(nèi)涵,以及如何通過(guò)System.Threading命名空間的類、委托和BackgroundWorker組件等三種手段構(gòu)建多線程應(yīng)用程序。
?本文如果能為剛接觸多線程的朋友起到拋磚引玉的作用也就心滿意足了。當(dāng)然,本人才疏學(xué)淺,文中難免會(huì)有不足或錯(cuò)誤的地方,懇請(qǐng)各位朋友多多指點(diǎn)。
1.理解多線程
?我們通常理解的應(yīng)用程序就是一個(gè)*.exe文件,當(dāng)運(yùn)行*.exe應(yīng)用程序以后,系統(tǒng)會(huì)在內(nèi)存中為該程序分配一定的空間,同時(shí)加載一些該程序所需的資源。其實(shí)這就可以稱為創(chuàng)建了一個(gè)進(jìn)程,可以通過(guò)Windows任務(wù)管理器查看這個(gè)進(jìn)程的相關(guān)信息,如映像名稱、用戶名、內(nèi)存使用、PID(唯一的進(jìn)程標(biāo)示)等,如圖下所示。
而線程則只是進(jìn)程中的一個(gè)基本執(zhí)行單元。一個(gè)應(yīng)用程序往往只有一個(gè)程序入口,如:
??? ????[STAThread]
? ? ? ??static?void?Main()???//應(yīng)用程序主入口點(diǎn)
??????? {
????????????Application.EnableVisualStyles();
????????????Application.SetCompatibleTextRenderingDefault(false);
????????????Application.Run(new?MainForm());
? ? } 進(jìn)程會(huì)包含一個(gè)進(jìn)入此入口的線程,我們稱之為主線程。其中,特性?[STAThread]?指示應(yīng)用程序的默認(rèn)線程模型是單線程單元(相關(guān)信息可參考
http://msdn.microsoft.com/en-us/library/system.stathreadattribute(VS.71).aspx)。
只包含一個(gè)主線程的進(jìn)程是線程安全的,相當(dāng)于程序僅有一條工作線,只有完成了前面的任務(wù)才能執(zhí)行排在后面的任務(wù)。
?然當(dāng)在程序處理一個(gè)很耗時(shí)的任務(wù),如輸出一個(gè)大的文件或遠(yuǎn)程訪問(wèn)數(shù)據(jù)庫(kù)等,此時(shí)的窗體界面程序?qū)τ脩舳曰鞠袷菦]反應(yīng)一樣,菜單、按鈕等都用不了。因?yàn)榇绑w上控件的響應(yīng)事件也是需要主線程來(lái)執(zhí)行的,而主線程正忙著干其他的事,控件響應(yīng)事件就只能排隊(duì)等著主線程忙完了再執(zhí)行。
?為了克服單線程的這個(gè)缺陷,Win32 API可以讓主線程再創(chuàng)建其他的次線程,但不論是主線程還是次線程都是進(jìn)程中獨(dú)立的執(zhí)行單元,可以同時(shí)訪問(wèn)共享的數(shù)據(jù),這樣就有了多線程這個(gè)概念。
?相信到這,應(yīng)該對(duì)多線程有個(gè)比較感性的認(rèn)識(shí)了。但筆者在這要提醒一下,基于單核計(jì)算機(jī)的多線程其實(shí)只是操作系統(tǒng)施展的一個(gè)障眼法而已(但這不會(huì)干擾我們理解構(gòu)建多線程應(yīng)用程序的思路),他并不能縮短完成所有任務(wù)的時(shí)間,有時(shí)反而還會(huì)因?yàn)槭褂眠^(guò)多的線程而降低性能、延長(zhǎng)時(shí)間。之所以這樣,是因?yàn)閷?duì)于單CPU而言,在一個(gè)單位時(shí)間(也稱時(shí)間片)內(nèi),只能執(zhí)行一個(gè)線程,即只能干一件事。當(dāng)一個(gè)線程的時(shí)間片用完時(shí),系統(tǒng)會(huì)將該線程掛起,下一個(gè)時(shí)間內(nèi)再執(zhí)行另一個(gè)線程,如此,CPU以時(shí)間片為間隔在多個(gè)線程之間交替執(zhí)行運(yùn)算(其實(shí)這里還與每個(gè)線程的優(yōu)先級(jí)有關(guān),級(jí)別高的會(huì)優(yōu)先處理)。由于交替時(shí)間間隔很短,所以造成了各個(gè)線程都在“同時(shí)”工作的假象;而如果線程數(shù)目過(guò)多,由于系統(tǒng)掛起線程時(shí)要記錄線程當(dāng)前的狀態(tài)數(shù)據(jù)等,這樣又勢(shì)必會(huì)降低程序的整體性能。但對(duì)于這些,多核計(jì)算機(jī)就能從本質(zhì)上(真正的同時(shí)工作)提高程序的執(zhí)行效率。2.?線程異步與線程同步
?從線程執(zhí)行任務(wù)的方式上可以分為線程同步和線程異步。而為了方便理解,后面描述中用“同步線程”指代與線程同步相關(guān)的線程,同樣,用“異步線程”表示與線程異步相關(guān)的線程。
?線程異步就是解決類似前面提到的執(zhí)行耗時(shí)任務(wù)時(shí)界面控件不能使用的問(wèn)題。如創(chuàng)建一個(gè)次線程去專門執(zhí)行耗時(shí)的任務(wù),而其他如界面控件響應(yīng)這樣的任務(wù)交給另一個(gè)線程執(zhí)行(往往由主線程執(zhí)行)。這樣,兩個(gè)線程之間通過(guò)線程調(diào)度器短時(shí)間(時(shí)間片)內(nèi)的切換,就模擬出多個(gè)任務(wù)“同時(shí)”被執(zhí)行的效果。
?線程異步往往是通過(guò)創(chuàng)建多個(gè)線程執(zhí)行多個(gè)任務(wù),多個(gè)工作線同時(shí)開工,類似多輛在寬廣的公路上并行的汽車同時(shí)前進(jìn),互不干擾(讀者要明白,本質(zhì)上并沒有“同時(shí)”,僅僅是操作系統(tǒng)玩的一個(gè)障眼法。但這個(gè)障眼法卻對(duì)提高我們的程序與用戶之間的交互、以及提高程序的友好性很有用,不是嗎)。
?在介紹線程同步之前,先介紹一個(gè)與此緊密相關(guān)的概念——并發(fā)問(wèn)題。
?前面提到,線程都是獨(dú)立的執(zhí)行單元,可以訪問(wèn)共享的數(shù)據(jù)。也就是說(shuō),在一個(gè)擁有多個(gè)次線程的程序中,每個(gè)線程都可以訪問(wèn)同一個(gè)共享的數(shù)據(jù)。再稍加思考你會(huì)發(fā)現(xiàn)這樣可能會(huì)出問(wèn)題:由于線程調(diào)度器會(huì)隨機(jī)的掛起某一個(gè)線程(前面介紹的線程間的切換),所以當(dāng)線程a對(duì)共享數(shù)據(jù)D的訪問(wèn)(修改、刪除等操作)完成之前被掛起,而此時(shí)線程b又恰好去訪問(wèn)數(shù)據(jù)D,那么線程b訪問(wèn)的則是一個(gè)不穩(wěn)定的數(shù)據(jù)。這樣就會(huì)產(chǎn)生非常難以發(fā)現(xiàn)bug,由于是隨機(jī)發(fā)生的,產(chǎn)生的結(jié)果是不可預(yù)測(cè)的,這樣樣的bug也都很難重現(xiàn)和調(diào)試。這就是并發(fā)問(wèn)題。
?為了解決多線程共同訪問(wèn)一個(gè)共享資源(也稱互斥訪問(wèn))時(shí)產(chǎn)生的并發(fā)問(wèn)題,線程同步就應(yīng)運(yùn)而生了。線程同步的機(jī)理,簡(jiǎn)單的說(shuō),就是防止多個(gè)線程同時(shí)訪問(wèn)某個(gè)共享的資源。做法很簡(jiǎn)單,標(biāo)記訪問(wèn)某共享資源的那部分代碼,當(dāng)程序運(yùn)行到有標(biāo)記的地方時(shí),CLR(具體是什么可以先不管,只要知道它能控制就行)對(duì)各線程進(jìn)行調(diào)整:如果已有線程在訪問(wèn)一資源,CLR就會(huì)將其他訪問(wèn)這一資源的線程掛起,直到前一線程結(jié)束對(duì)該資源的訪問(wèn)。這樣就保證了同一時(shí)間只有一個(gè)線程訪問(wèn)該資源。打個(gè)比方,就如某資源放在只有一獨(dú)木橋相連的孤島上,如果要使用該資源,大家就得排隊(duì),一個(gè)一個(gè)來(lái),前面的回來(lái)了,下一個(gè)再去,前面的沒回來(lái),后面的就原地待命。
?這里只是把基本的概念及原理做了一個(gè)簡(jiǎn)單的闡述,不至于看后面的程序時(shí)糊里糊涂的。具體如何編寫代碼,下面的段落將做詳細(xì)介紹。3.創(chuàng)建多線程應(yīng)用程序 ? ?這里做一個(gè)簡(jiǎn)單的說(shuō)明:下面主要通過(guò)介紹通過(guò)System.Threading命名空間的類、委托和BackgroundWorker組件三種不同的手段構(gòu)建多線程應(yīng)用程序,具體會(huì)從線程異步和線程同步兩個(gè)方面來(lái)闡述。
3.1通過(guò)System.Threading命名空間的類構(gòu)建
????在.NET平臺(tái)下,System.Threading命名空間提供了許多類型來(lái)構(gòu)建多線程應(yīng)用程序,可以說(shuō)是專為多線程服務(wù)的。由于本文僅是想起到一個(gè)“拋磚引玉”的作用,所以對(duì)于這一塊不會(huì)探討過(guò)多、過(guò)深,主要使用System.Threading.Thread類。
?先從System.Threading.Thread類本身相關(guān)的一個(gè)小例子說(shuō)起,代碼如下,解釋見注釋: using?System;
using?System.Threading;?//引入System.Threading命名空間
namespace?MultiThread
{
????class?Class
??? {
????????static?void?Main(string[] args)
??????? {
????????????Console.WriteLine("**************?顯示當(dāng)前線程的相關(guān)信息 *************");
????????????//聲明線程變量并賦值為當(dāng)前線程
????????????Thread?primaryThread =?Thread.CurrentThread;
????????????//賦值線程的名稱
?????????? ?primaryThread.Name =?"主線程";
????????????//顯示線程的相關(guān)信息
????????????Console.WriteLine("線程的名字:{0}", primaryThread.Name);
????????????Console.WriteLine("線程是否啟動(dòng)? {0}", primaryThread.IsAlive);
????????????Console.WriteLine("線程的優(yōu)先級(jí): {0}", primaryThread.Priority);
????????????Console.WriteLine("線程的狀態(tài): {0}", primaryThread.ThreadState);
????????????Console.ReadLine();
??????? }
??? }
}
輸出結(jié)果如下:
**************?顯示當(dāng)前線程的相關(guān)信息 *************
線程的名字:主線程
線程是否啟動(dòng)? True
線程的優(yōu)先級(jí): Normal
線程的狀態(tài): Running
?????對(duì)于上面的代碼不想做過(guò)多解釋,只說(shuō)一下Thread.CurrentThread得到的是執(zhí)行當(dāng)前代碼的線程。3.1.1異步調(diào)用線程
????這里先說(shuō)一下前臺(tái)線程與后臺(tái)線程。前臺(tái)線程能阻止應(yīng)用程序的終止,既直到所有前臺(tái)線程終止后才會(huì)徹底關(guān)閉應(yīng)用程序。而對(duì)后臺(tái)線程而言,當(dāng)所有前臺(tái)線程終止時(shí),后臺(tái)線程會(huì)被自動(dòng)終止,不論后臺(tái)線程是否正在執(zhí)行任務(wù)。默認(rèn)情況下通過(guò)Thread.Start()方法創(chuàng)建的線程都自動(dòng)為前臺(tái)線程,把線程的屬性IsBackground設(shè)為true時(shí)就將線程轉(zhuǎn)為后臺(tái)線程。
?????下面先看一個(gè)例子,該例子創(chuàng)建一個(gè)次線程執(zhí)行打印數(shù)字的任務(wù),而主線程則干其他的事,兩者同時(shí)進(jìn)行,互不干擾。
using?System;
using?System.Threading;
using?System.Windows.Forms;
namespace?MultiThread
{
????class?Class
??? {
????????static?void?Main(string[] args)
??????? {
????????????Console.WriteLine("*************?兩個(gè)線程同時(shí)工作 *****************");
?????????? ?//主線程,因?yàn)楂@得的是當(dāng)前在執(zhí)行Main()的線程
????????????Thread?primaryThread =?Thread.CurrentThread;
??????????? primaryThread.Name =?"主線程";
????????????Console.WriteLine("-> {0}?在執(zhí)行主函數(shù) Main()。",?Thread.CurrentThread.Name);
????????????//次線程,該線程指向PrintNumbers()方法
??????? ????Thread?SecondThread =?new?Thread(new?ThreadStart(PrintNumbers));
??????????? SecondThread.Name =?"次線程";
????????????//次線程開始執(zhí)行指向的方法
??????????? SecondThread.Start();
?
????????????//同時(shí)主線程在執(zhí)行主函數(shù)中的其他任務(wù)
????????????MessageBox.Show("正在執(zhí)行主函數(shù)中的任務(wù)。。。。",?"主線程在工作...");
????????????Console.ReadLine();
??????? }
????????//打印數(shù)字的方法
????????static?void?PrintNumbers()
??????? {
????????????Console.WriteLine("-> {0}?在執(zhí)行打印數(shù)字函數(shù) PrintNumber()",?Thread.CurrentThread.Name);
????????????Console.WriteLine("打印數(shù)字: ");
?????????? ?for?(int?i = 0; i < 10; i++)
??????????? {
????????????????Console.Write("{0}, ", i);
????????????????//Sleep()方法使當(dāng)前線程掛等待指定的時(shí)長(zhǎng)在執(zhí)行,這里主要是模仿打印任務(wù)
????????????????Thread.Sleep(2000);
??????????? }
????????????Console.WriteLine();
??????? }
??? }
}
程序運(yùn)行后會(huì)看到一個(gè)窗口彈出,如圖所示,同時(shí)控制臺(tái)窗口也在不斷的顯示數(shù)字。
?
輸出結(jié)果為:
?
*************?兩個(gè)線程同時(shí)工作 *****************
->?主線程 在執(zhí)行主函數(shù) Main()。
->?次線程 在執(zhí)行打印數(shù)字函數(shù) PrintNumber()
打印數(shù)字:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
????這里稍微對(duì) Thread SecondThread = new Thread(new ThreadStart(PrintNumbers)); 這一句做個(gè)解釋。其實(shí) ThreadStart 是 System.Threading 命名空間下的一個(gè)委托,其聲明是 public delegate void ThreadStart(),指向不帶參數(shù)、返回值為空的方法。所以當(dāng)使用 ThreadStart 時(shí),對(duì)應(yīng)的線程就只能調(diào)用不帶參數(shù)、返回值為空的方法。那非要指向含參數(shù)的方法呢?在System.Threading命名空間下還有一個(gè)ParameterizedThreadStart 委托,其聲明是 public delegate void ParameterizedThreadStart(object obj),可以指向含 object 類型參數(shù)的方法,這里不要忘了 object 可是所有類型的父類哦,有了它就可以通過(guò)創(chuàng)建各種自定義類型,如結(jié)構(gòu)、類等傳遞很多參數(shù)了,這里就不再舉例說(shuō)明了。
3.1.2并發(fā)問(wèn)題
??????這里再通過(guò)一個(gè)例子讓大家切實(shí)體會(huì)一下前面說(shuō)到的并發(fā)問(wèn)題,然后再介紹線程同步。
using?System;
using?System.Threading;
namespace?MultiThread1
{
????class?Class
??? {
????????static?void?Main(string[] args)
??????? {
????????????Console.WriteLine("*********?并發(fā)問(wèn)題演示 ***************");
????????????//創(chuàng)建一個(gè)打印對(duì)象實(shí)例
????????????Printer?printer =?new?Printer();
????????????//聲明一含5個(gè)線程對(duì)象的數(shù)組
????????????Thread[] threads =?new?Thread[10];
?
????????????for?(int?i = 0; i < 10; i++)
??????????? {
????????????????//將每一個(gè)線程都指向printer的PrintNumbers()方法
??????????????? threads[i] =?new?Thread(new?ThreadStart(printer.PrintNumbers));
????????????????//給每一個(gè)線程編號(hào)
??????????????? threads[i].Name = i.ToString() +"號(hào)線程";
?? ?????????}
?
????????????//開始執(zhí)行所有線程
????????????foreach?(Thread?t?in?threads)
??????????????? t.Start();
????????????Console.ReadLine();
??????? }
??? }
????//打印類
????public?class?Printer
??? {
????????//打印數(shù)字的方法
????????public?void?PrintNumbers()
??????? {
????????????Console.WriteLine("-> {0}?正在執(zhí)行打印任務(wù),開始打印數(shù)字:",?Thread.CurrentThread.Name);
?
????????????for?(int?i = 0; i < 10; i++)
??????????? {
????????????????Random?r =?new?Random();
????????????????//為了增加沖突的幾率及,使各線程各自等待隨機(jī)的時(shí)長(zhǎng)
????????????????Thread.Sleep(2000 * r.Next(5));????
????????????????//打印數(shù)字
????????????????Console.Write("{0} ", i);
??????????? }
????????????Console.WriteLine();
??????? }
??? }
}
上面的例子中,主線程產(chǎn)生的10個(gè)線程同時(shí)訪問(wèn)同一個(gè)對(duì)象實(shí)例printer的方法PrintNumbers(),由于沒有鎖定共享資源(注意,這里是指控制臺(tái)),所以在PrintNumbers()輸出到控制臺(tái)之前,調(diào)用PrintNumbers()的線程很可能被掛起,但不知道什么時(shí)候(或是否有)掛起,導(dǎo)致得到不可預(yù)測(cè)的結(jié)果。如下是兩個(gè)不同的結(jié)果(當(dāng)然,讀者的運(yùn)行結(jié)果可能會(huì)是其他情形)。
?
情形一 情形二
3.1.3線程同步
????線程同步的訪問(wèn)方式也稱為阻塞調(diào)用,即沒有執(zhí)行完任務(wù)不返回,線程被掛起。可以使用C#中的lock關(guān)鍵字,在此關(guān)鍵字范圍類的代碼都將是線程安全的。lock關(guān)鍵字需定義一個(gè)標(biāo)記,線程進(jìn)入鎖定范圍是必須獲得這個(gè)標(biāo)記。當(dāng)鎖定的是一個(gè)實(shí)例級(jí)對(duì)象的私有方法時(shí)使用方法本身所在對(duì)象的引用就可以了,將上面例子中的打印類Printer稍做改動(dòng),添加lock關(guān)鍵字,代碼如下:
????//打印類
????public?class?Printer
??? {
????????public?void?PrintNumbers()
??????? {
????????????//使用lock關(guān)鍵字,鎖定d的代碼是線程安全的
????????????lock?(this)
??????????? {
????????????????Console.WriteLine("-> {0}?正在執(zhí)行打印任務(wù),開始打印數(shù)字:",?Thread.CurrentThread.Name);
?
????????????????for?(int?i = 0; i < 10; i++)
??????????????? {
????????????????????Random?r =?new?Random();
????????????????????//為了增加沖突的幾率及,使各線程各自等待隨機(jī)的時(shí)長(zhǎng)
????????????????????Thread.Sleep(2000 * r.Next(5));
????????????????????//打印數(shù)字
????????????????????Console.Write("{0} ", i);
??????????????? }
????????????????Console.WriteLine();
??????????? }
??????? }
??? }
}
同步后執(zhí)行結(jié)果如下:
?
也可以使用System.Threading命名空間下的Monitor類進(jìn)行同步,兩者內(nèi)涵是一樣的,但Monitor類更靈活,這里就不在做過(guò)多的探討,代碼如下:
????
?
????//打印類
????public?class?Printer
??? {
????????public?void?PrintNumbers()
??????? {
????????????Monitor.Enter(this);
????????????try
??????????? {
????????????????Console.WriteLine("-> {0}?正在執(zhí)行打印任務(wù),開始打印數(shù)字:",?Thread.CurrentThread.Name);
?
????????????????for?(int?i = 0; i < 10; i++)
??????????????? {
????????????????????Random?r =?new?Random();
????????????????????//為了增加沖突的幾率及,使各線程各自等待隨機(jī)的時(shí)長(zhǎng)
????????????????????Thread.Sleep(2000 * r.Next(5));
????????????????????//打印數(shù)字
????????????????????Console.Write("{0} ", i);
??????????????? }
????????????????Console.WriteLine();
??????????? }
????????????finally
??????????? {
????????????????Monitor.Exit(this);
??????????? }
??????? }
}
輸出結(jié)果與上面的一樣。3.2通過(guò)委托構(gòu)建多線程應(yīng)用程序
? ??在看下面的內(nèi)容時(shí)要求對(duì)委托有一定的了解,如果不清楚的話推薦參考一下博客園張子陽(yáng)的《C# 中的委托和事件》,里面對(duì)委托與事件進(jìn)行由淺入深的較系統(tǒng)的講解:http://www.cnblogs.com/JimmyZhang/archive/2007/09/23/903360.html。
?????這里先舉一個(gè)關(guān)于委托的簡(jiǎn)單例子,具體解說(shuō)見注釋:
using?System;
namespace?MultiThread
{
????//定義一個(gè)指向包含兩個(gè)int型參數(shù)、返回值為int型的函數(shù)的委托
????public?delegate?int?AddOp(int?x,?int?y);
????class?program
??? {
????????static?void?Main(string[] args)
??????? {
????????????//創(chuàng)建一個(gè)指向Add()方法的AddOp對(duì)象p
????????????AddOp?pAddOp =?new?AddOp(Add);
????????????//使用委托間接調(diào)用方法Add()
????????????Console.WriteLine("10 + 25 = {0}", pAddOp(10, 5));
????????????Console.ReadLine();
??????? }
????????//求和的函數(shù)
????????static?int?Add(int?x,?int?y)
??????? {
????????????int?sum = x + y;
????????????return?sum;
??????? }
??? }
}?
運(yùn)行結(jié)果為:
10 + 25 = 153.2.1線程異步
????先說(shuō)明一下,這里不打算講解委托線程異步或同步的參數(shù)傳遞、獲取返回值等,只是做個(gè)一般性的開頭而已,如果后面有時(shí)間了再另外寫一篇關(guān)于多線程中參數(shù)傳遞、獲取返回值的文章。
?????注意觀察上面的例子會(huì)發(fā)現(xiàn),直接使用委托實(shí)例?pAddOp(10, 5)?就調(diào)用了求和方法 Add()。很明顯,這個(gè)方法是由主線程執(zhí)行的。然而,委托類型中還有另外兩個(gè)方法——BeginInvoke()和EndInvoke(),下面通過(guò)具體的例子來(lái)說(shuō)明,將上面的例子做適當(dāng)改動(dòng),如下:
using?System;
using?System.Threading;
using?System.Runtime.Remoting.Messaging;
namespace?MultiThread
{
????//聲明指向含兩個(gè)int型參數(shù)、返回值為int型的函數(shù)的委托
????public?delegate?int?AddOp(int?x,?int?y);
????class?Program
??? {
????????static?void?Main(string[] args)
??????? {
????????????Console.WriteLine("*******?委托異步線程 兩個(gè)線程“同時(shí)”工作 *********");
????????????//顯示主線程的唯一標(biāo)示
????????????Console.WriteLine("調(diào)用Main()的主線程的線程ID是:{0}.",?Thread.CurrentThread.ManagedThreadId);
????????????//將委托實(shí)例指向Add()方法
????????????AddOp?pAddOp =?new?AddOp(Add);
????????????//開始委托次線程調(diào)用。委托BeginInvoke()方法返回的類型是IAsyncResult,
????????????//包含這委托指向方法結(jié)束返回的值,同時(shí)也是EndInvoke()方法參數(shù)
????????????IAsyncResult?iftAR = pAddOp.BeginInvoke(10, 10,?null,?null);
????????????Console.WriteLine(""nMain()方法中執(zhí)行其他任務(wù)........"n");
????????????int?sum = pAddOp.EndInvoke(iftAR);
?????? ?????Console.WriteLine("10 + 10 = {0}.", sum);
????????????Console.ReadLine();
??????? }
????????//求和方法
????????static?int?Add(int?x,?int?y)
??????? {
????????????//指示調(diào)用該方法的線程ID,ManagedThreadId是線程的唯一標(biāo)示
????????????Console.WriteLine("調(diào)用求和方法 Add()的線程ID是: {0}.",?Thread.CurrentThread.ManagedThreadId);
????????????//模擬一個(gè)過(guò)程,停留5秒
????????????Thread.Sleep(5000);
????????????int?sum = x + y;
????????????return?sum;
??????? }
??? }
}
運(yùn)行結(jié)果如下:
*******?委托異步線程 兩個(gè)線程“同時(shí)”工作 *********
調(diào)用Main()的主線程的線程ID是:10.
?
Main()方法中執(zhí)行其他任務(wù)........
?
調(diào)用求和方法 Add()的線程ID是: 7.
10 + 10 = 20.3.2.2線程同步
?????委托中的線程同步主要涉及到上面使用的pAddOp.BeginInvoke(10, 10, null, null)方法中后面兩個(gè)為null的參數(shù),具體的可以參考相關(guān)資料。這里代碼如下,解釋見代碼注釋:
using?System;
using?System.Threading;
using?System.Runtime.Remoting.Messaging;
namespace?MultiThread
{
????//聲明指向含兩個(gè)int型參數(shù)、返回值為int型的函數(shù)的委托
????public?delegate?int?AddOp(int?x,?int?y);
????class?Program
??? {
????????static?void?Main(string[] args)
??????? {
????????????Console.WriteLine("*******?線程同步,“阻塞”調(diào)用,兩個(gè)線程工作 *********");
????????????Console.WriteLine("Main() invokee on thread {0}.",?Thread.CurrentThread.ManagedThreadId);
//將委托實(shí)例指向Add()方法
????????????AddOp?pAddOp =?new?AddOp(Add);
????????????IAsyncResult?iftAR = pAddOp.BeginInvoke(10, 10,?null,?null);
????????????//判斷委托線程是否執(zhí)行完任務(wù),
????????????//沒有完成的話,主線程就做其他的事
????????????while?(!iftAR.IsCompleted)
??????????? {
????????????????Console.WriteLine("Main()方法工作中.......");
????????????????Thread.Sleep(1000);
??????????? }
????????????//獲得返回值
????????????int?answer = pAddOp.EndInvoke(iftAR);
????????????Console.WriteLine("10 + 10 = {0}.", answer);
????????????Console.ReadLine();
??????? }
????????//求和方法
????????static?int?Add(int?x,?int?y)
??????? {
????????????//指示調(diào)用該方法的線程ID,ManagedThreadId是線程的唯一標(biāo)示
????????????Console.WriteLine("調(diào)用求和方法 Add()的線程ID是: {0}.",?Thread.CurrentThread.ManagedThreadId);
????????????//模擬一個(gè)過(guò)程,停留5秒
????????????Thread.Sleep(5000);
????????????int?sum = x + y;
????????????return?sum;
??????? }
??? }
}
運(yùn)行結(jié)果如下:
*******?線程同步,“阻塞”調(diào)用,兩個(gè)線程工作 *********
Main() invokee on thread 10.
Main()方法工作中.......
調(diào)用求和方法 Add()的線程ID是: 7.
Main()方法工作中.......
Main()方法工作中.......
Main()方法工作中.......
Main()方法工作中.......
10 + 10 = 20.3.3BackgroundWorker組件
??????BackgroundWorker組件位于工具箱中,用于方便的創(chuàng)建線程異步的程序。新建一個(gè)WindowsForms應(yīng)用程序,界面如下:
?
????????private?void?button1_Click(object?sender,?EventArgs?e)
代碼如下,解釋參見注釋:
?
??????? {
????????????try
??????????? {
????????????????//獲得輸入的數(shù)字
????????????????int?numOne =?int.Parse(this.textBox1.Text);
????????????????int?numTwo =?int.Parse(this.textBox2.Text);
????????????????//實(shí)例化參數(shù)類
?????????????? ?AddParams?args =?new?AddParams(numOne, numTwo);
????????????????//調(diào)用RunWorkerAsync()生成后臺(tái)線程,同時(shí)傳入?yún)?shù)
????????????????this.backgroundWorker1.RunWorkerAsync(args);
??????????? }
????????????catch?(Exception?ex)
??????????? {
????????????????MessageBox.Show(ex.Message);
??????????? }
??????? }
?
????????//backgroundWorker新生成的線程開始工作
????????private?void?backgroundWorker1_DoWork(object?sender,?DoWorkEventArgs?e)
??????? {
????????????//獲取傳入的AddParams對(duì)象
????????????AddParams?args = (AddParams)e.Argument;
?????????? ?//停留5秒,模擬耗時(shí)任務(wù)
????????????Thread.Sleep(5000);
????????????//返回值
??????????? e.Result = args.a + args.b;
??????? }
?
????????//當(dāng)backgroundWorker1的DoWork中的代碼執(zhí)行完后會(huì)觸發(fā)該事件
????????//同時(shí),其執(zhí)行的結(jié)果會(huì)包含在RunWorkerCompletedEventArgs參數(shù)中
????????private?void?backgroundWorker1_RunWorkerCompleted(object?sender,?RunWorkerCompletedEventArgs?e)
??????? {
????????????//顯示運(yùn)算結(jié)果
????????????MessageBox.Show("運(yùn)行結(jié)果為:"?+ e.Result.ToString(),?"結(jié)果");
??????? }
??? }
?
????//參數(shù)類,這個(gè)類僅僅起到一個(gè)記錄并傳遞參數(shù)的作用
????class?AddParams
??? {
????????public?int?a, b;
????????public?AddParams(int?numb1,?int?numb2)
??????? {
??????????? a = numb1;
??????????? b = numb2;
??????? }
??? }
注意,在計(jì)算結(jié)果的同時(shí),窗體可以隨意移動(dòng),也可以重新在文本框中輸入信息,這就說(shuō)明主線程與backgroundWorker組件生成的線程是異步的。4.總結(jié)
?????本文從線程、進(jìn)程、應(yīng)用程序的關(guān)系開始,介紹了一些關(guān)于多線程的基本概念,同時(shí)闡述了線程異步、線程同步及并發(fā)問(wèn)題等。最后從應(yīng)用角度出發(fā),介紹了如何通過(guò)System.Threading命名空間的類、委托和BackgroundWorker組件等三種手段構(gòu)建多線程應(yīng)用程序。
?再次說(shuō)一下,由于本人才疏學(xué)淺,文中難免會(huì)有不足或錯(cuò)誤的地方,真誠(chéng)期盼各位同道朋友批評(píng)指正。
總結(jié)
以上是生活随笔為你收集整理的浅析C#中构建多线程应用程序的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: U8安装包下载地址
- 下一篇: 明月浩空播放器php源码,明月浩空音乐播