解析C#多線程編程中異步多線程的實現(xiàn)及線程池的使用
0、線程的本質(zhì)
線程不是一個計算機硬件的功能,而是操作系統(tǒng)提供的一種邏輯功能,線程本質(zhì)上是進程中一段并發(fā)運行的代碼,所以線程需要操作系統(tǒng)投入CPU資源來運行和調(diào)度。
1、多線程:
使用多個處理句柄同時對多個任務(wù)進行控制處理的一種技術(shù)。據(jù)博主的理解,多線程就是該應(yīng)用的主線程任命其他多個線程去協(xié)助它完成需要的功能,并且主線程和協(xié)助線程是完全獨立進行的。不知道這樣說好不好理解,后面慢慢在使用中會有更加詳細(xì)的講解。
2、多線程的使用:
(1)最簡單、最原始的使用方法:Thread oGetArgThread = new Thread(new ThreadStart(() =>{});這種用法應(yīng)該大多數(shù)人都使用過,參數(shù)為一個ThreadStart類型的委托。將ThreadStart轉(zhuǎn)到定義可知:
public delegate void ThreadStart();
它是一個沒有參數(shù),沒有返回值的委托。所以他的使用如下:
static void Main(string[] args) { Thread oGetArgThread = new Thread(new ThreadStart(Test)); oGetArgThread.IsBackground = true; oGetArgThread.Start(); for (var i = 0; i < 1000000; i++) { Console.WriteLine("主線程計數(shù)" + i); //Thread.Sleep(100); } } private static void Test() { for (var i = 0; i < 1000000; i++) { Console.WriteLine("后臺線程計數(shù)" + i); //Thread.Sleep(100); } } 定義一個沒有參數(shù)沒有返回值的方法傳入該委托。當(dāng)然也可以不定義方法寫成匿名方法: static void Main(string[] args) { Thread oGetArgThread = new Thread(new System.Threading.ThreadStart(() => { for (var i = 0; i < 1000000; i++) { Console.WriteLine("后臺線程計數(shù)" + i); //Thread.Sleep(100); } })); oGetArgThread.IsBackground = true; oGetArgThread.Start();
這個和上面的意義相同。得到的結(jié)果如下:
說明主線程和后臺線程是互相獨立的。由系統(tǒng)調(diào)度資源去執(zhí)行。
如果這樣那有人就要問了,如果我需要多線程執(zhí)行的方法有參數(shù)或者有返回值或者既有參數(shù)又有返回值呢。。。別著急我們來看看new Thread()的幾個構(gòu)造函數(shù):
public Thread(ParameterizedThreadStart start); public Thread(ThreadStart start); public Thread(ParameterizedThreadStart start, int maxStackSize); public Thread(ThreadStart start, int maxStackSize);
轉(zhuǎn)到定義可知參數(shù)有兩類,一類是無參無返回值的委托,另一類是有參無返回值的委托。對于有參數(shù)的委托使用方法:
static void Main(string[] args) { Thread oThread = new Thread(new ParameterizedThreadStart(Test2)); oThread.IsBackground = true; oThread.Start(1000); } private static void Test2(object Count) { for (var i = 0; i < (int)Count; i++) { Console.WriteLine("后臺線程計數(shù)" + i); //Thread.Sleep(100); } }
對于有參又有返回值的委托,很顯然使用new Thread()這種方式是沒有解決方案的。其實對于有參又有返回值的委托可以使用異步來實現(xiàn):
public delegate string MethodCaller(string name);//定義個代理 MethodCaller mc = new MethodCaller(GetName); string name = "my name";//輸入?yún)?shù) IAsyncResult result = mc.BeginInvoke(name,null, null); string myname = mc.EndInvoke(result);//用于接收返回值 public string GetName(string name) // 函數(shù) { return name; }
關(guān)于這種方式還有幾點值得一說的是:
①
Thread oGetArgThread = new Thread(new ThreadStart(Test)); oGetArgThread.Join();//主線程阻塞,等待分支線程運行結(jié)束,這一步看功能需求進行選擇,主要為了多個進程達(dá)到同步的效果
②線程的優(yōu)先級可以通過Thread對象的Priority屬性來設(shè)置,Priority屬性對應(yīng)一個枚舉:
public enum ThreadPriority { // 摘要: // 可以將 System.Threading.Thread 安排在具有任何其他優(yōu)先級的線程之后。 Lowest = 0, // // 摘要: // 可以將 System.Threading.Thread 安排在具有 Normal 優(yōu)先級的線程之后,在具有 Lowest 優(yōu)先級的線程之前。 BelowNormal = 1, // // 摘要: // 可以將 System.Threading.Thread 安排在具有 AboveNormal 優(yōu)先級的線程之后,在具有 BelowNormal 優(yōu)先級的線程之前。 // 默認(rèn)情況下,線程具有 Normal 優(yōu)先級。 Normal = 2, // // 摘要: // 可以將 System.Threading.Thread 安排在具有 Highest 優(yōu)先級的線程之后,在具有 Normal 優(yōu)先級的線程之前。 AboveNormal = 3, // // 摘要: // 可以將 System.Threading.Thread 安排在具有任何其他優(yōu)先級的線程之前。 Highest = 4, }
從0到4,優(yōu)先級由低到高。
③關(guān)于多個線程同時使用一個對象或資源的情況,也就是線程的資源共享,為了避免數(shù)據(jù)紊亂,一般采用.Net悲觀鎖lock的方式處理。
private static object oLock = new object(); private static void Test2(object Count) { lock (oLock) { for (var i = 0; i < (int)Count; i++) { Console.WriteLine("后臺線程計數(shù)" + i); //Thread.Sleep(100); } } }
(2)Task方式使用多線程:
這種方式一般用在需要循環(huán)處理某項業(yè)務(wù)并且需要得到處理后的結(jié)果。使用代碼如下:
List<Task> lstTaskBD = new List<Task>(); foreach (var bd in lstBoards) { var bdTmp = bd;//這里必須要用一個臨時變量 var oTask = Task.Factory.StartNew(() => { var strCpBdCmd = "rm -Rf " + bdTmp.Path + "/*;cp -R " + CombineFTPPaths(FTP_EMULATION_BD_ROOT, "bd_correct") + "/* " + bdTmp.Path + "/"; oPlink.Run(bdTmp.EmulationServer.BigIP, bdTmp.EmulationServer.UserName, bdTmp.EmulationServer.Password, strCpBdCmd); Thread.Sleep(500); }); lstTaskBD.Add(oTask); } Task.WaitAll(lstTaskBD.ToArray());//等待所有線程只都行完畢
使用這種方式的時候需要注意這一句 var bdTmp = bd;這里必須要用一個臨時變量,要不然多個bd對象容易串?dāng)?shù)據(jù)。如果有興趣可以調(diào)試看看。這種方法比較簡單,就不多說了。當(dāng)然Task對象的用法肯定遠(yuǎn)不止如此,還涉及到任務(wù)的調(diào)度等復(fù)雜的邏輯。博主對這些東西理解有限,就不講解了。
(3)異步操作的本質(zhì)
所有的程序最終都會由計算機硬件來執(zhí)行,所以為了更好的理解異步操作的本質(zhì),我們有必要了解一下它的硬件基礎(chǔ)。 熟悉電腦硬件的朋友肯定對DMA這個詞不陌生,硬盤、光驅(qū)的技術(shù)規(guī)格中都有明確DMA的模式指標(biāo),其實網(wǎng)卡、聲卡、顯卡也是有DMA功能的。DMA就是直 接內(nèi)存訪問的意思,也就是說,擁有DMA功能的硬件在和內(nèi)存進行數(shù)據(jù)交換的時候可以不消耗CPU資源。只要CPU在發(fā)起數(shù)據(jù)傳輸時發(fā)送一個指令,硬件就開 始自己和內(nèi)存交換數(shù)據(jù),在傳輸完成之后硬件會觸發(fā)一個中斷來通知操作完成。這些無須消耗CPU時間的I/O操作正是異步操作的硬件基礎(chǔ)。所以即使在DOS 這樣的單進程(而且無線程概念)系統(tǒng)中也同樣可以發(fā)起異步的DMA操作。
(4)異步操作的優(yōu)缺點
因為異步操作無須額外的線程負(fù)擔(dān),并且使用回調(diào)的方式進行處理,在設(shè)計良好的情況下,處理函數(shù)可以不必使用共享變量(即使無法完全不用,最起碼可以減少 共享變量的數(shù)量),減少了死鎖的可能。當(dāng)然異步操作也并非完美無暇。編寫異步操作的復(fù)雜程度較高,程序主要使用回調(diào)方式進行處理,與普通人的思維方式有些出入,而且難以調(diào)試。
3、線程池的用法:
一般由于考慮到服務(wù)器的性能等問題,保證一個時間段內(nèi)系統(tǒng)線程數(shù)量在一定的范圍,需要使用線程池的概念。大概用法如下:
public class CSpiderCtrl { //將線程池對象作為一個全局變量 static Semaphore semaphore; public static void Run() { //1. 創(chuàng)建 SuperLCBB客戶端對象 var oClient = new ServiceReference_SuperLCBB.SOAServiceClient(); //2.初始化的時候new最大的線程池個數(shù)255(這個數(shù)值根據(jù)實際情況來判斷,如果服務(wù)器上面的東西很少,則可以設(shè)置大點) semaphore = new Semaphore(250, 255); CLogService.Instance.Debug("又一輪定時采集..."); _TestBedGo(oClient); } //執(zhí)行多線程的方法 private static void _TestBedGo(ServiceReference_SuperLCBB.SOAServiceClient oClient) { List<string> lstExceptPDUs = new List<string>(){ "SUPERLABEXP" }; var oTestBedRes = oClient.GetTestBedExceptSomePDU(lstExceptPDUs.ToArray(), true); if (CKVRes.ERRCODE_SUCCESS != oTestBedRes.ErrCode) { CLogService.Instance.Error("xxx"); return; } var lstTestBed = oTestBedRes.ToDocumentsEx(); System.Threading.Tasks.Parallel.ForEach(lstTestBed, (oTestBed) => { //一次最多255個線程,超過255的必須等待線程池釋放一個線程出來才行 semaphore.WaitOne(); //CLogService.Instance.Info("開始采集測試床:" + oTestBed[TBLTestBed.PROP_NAME]); //Thread.Sleep(2000); var strTestBedName = oTestBed[TBLTestBed.PROP_NAME] as string; var strSuperDevIP = oTestBed[TBLTestBed.PROP_SUPERDEVIP] as string; var strTestBedGID = oTestBed[TBLTestBed.PROP_GID] as string; var strPdu = oTestBed[TBLTestBed.PROP_PDUGID] as string; Thread.Sleep(new Random().Next(1000, 5000)); var oGetRootDevicesByTestBedGIDRes = oClient.GetRootDevicesByTestBedGID(strTestBedGID); CLogService.Instance.Debug(strPdu + "——測試床Name:" + strTestBedName + "開始"); Stopwatch sp = new Stopwatch(); sp.Start(); if (oGetRootDevicesByTestBedGIDRes.ErrCode != CKVRes.ERRCODE_SUCCESS || oGetRootDevicesByTestBedGIDRes.Documents.Count < 2) { CLogService.Instance.Debug("shit -- 3實驗室中測試床Name:" + strTestBedName + "2完成異常0"); //這里很重要的一點,每一次return 前一定要記得釋放線程,否則這個一直會占用資源 semaphore.Release(); return; } var strXML = oGetRootDevicesByTestBedGIDRes.Documents[0]; var strExeName = oGetRootDevicesByTestBedGIDRes.Documents[1]; //var strExeName = "RateSpider"; var oSuperDevClient = new SuperDevClient(CSuperDev.ENDPOINT, string.Format(CSuperDev.SuperDevURL, strSuperDevIP)); try { oSuperDevClient.IsOK(); } catch (Exception) { CLogService.Instance.Error("測試床Name:" + strTestBedName + "異常,插件沒起"); semaphore.Release(); return; } //2.3.1.請求SuperDev.Server(SuperDevIP),發(fā)送Run(XML和Exename) var oRunExeRes = new CKVRes(); try { oRunExeRes = oSuperDevClient.RunExeEx(strExeName, false, new string[] { strXML }); } catch { //CLogService.Instance.Debug("測試床Name:" + strTestBedName + "異常:" + ex.Message); } sp.Stop(); CLogService.Instance.Debug(strPdu + "——測試床Name:" + strTestBedName + "完成時間" + sp.Elapsed); //每一個線程完畢后記得釋放資源 semaphore.Release(); }); } }
需要注意:Semaphore對象的數(shù)量需要根據(jù)服務(wù)器的性能來設(shè)定;System.Threading.Tasks.Parallel.ForEach這種方式表示同時啟動lstTestBed.Length個線程去做一件事情,可以理解為
foreach(var oTestbed in lstTestBed) { Thread oThread=new Thread(new ThreadStart({ ...})); }
(4) 多線程里面還有一個值得一說的SpinWait類,用于提供對基于自旋的等待的支持。也就是說支持重復(fù)執(zhí)行一個委托,知道滿足條件就返回,我們來看它的用法:
public static void SpinUntil(Func<bool> condition); public static bool SpinUntil(Func<bool> condition, int millisecondsTimeout); public static bool SpinUntil(Func<bool> condition, TimeSpan timeout);
這個方法有三個構(gòu)造函數(shù),后兩個需要傳入一個時間,表示如果再規(guī)定的時間內(nèi)還沒有返回則自動跳出,防止死循環(huán)。
SpinWait.SpinUntil(() => { bIsworking = m_oClient.isworking(new isworking()).result; return bIsworking == false; }, 600000); //如果等了10分鐘還在跳纖則跳出 if (bIsworking) { oRes.ErrCode = "false交換機跳纖時間超過10分鐘,請檢查異常再操作"; return oRes; }
4、多線程的優(yōu)缺點
多線程的優(yōu)點很明顯,線程中的處理程序依然是順序執(zhí)行,符合普通人的思維習(xí)慣,所以編程簡單。但是多線程的缺點也同樣明顯,線程的使用(濫用)會給系統(tǒng)帶來上下文切換的額外負(fù)擔(dān)。并且線程間的共享變量可能造成死鎖的出現(xiàn)。
5、適用范圍
在了解了線程與異步操作各自的優(yōu)缺點之后,我們可以來探討一下線程和異步的合理用途。我認(rèn)為:當(dāng)需要執(zhí)行I/O操作時,使用異步操作比使用線程+同步 I/O操作更合適。I/O操作不僅包括了直接的文件、網(wǎng)絡(luò)的讀寫,還包括數(shù)據(jù)庫操作、Web Service、HttpRequest以及.net Remoting等跨進程的調(diào)用。
而線程的適用范圍則是那種需要長時間CPU運算的場合,例如耗時較長的圖形處理和算法執(zhí)行。但是往往由于使用線程編程的簡單和符合習(xí)慣,所以很多朋友往往會使用線程來執(zhí)行耗時較長的I/O操作。這樣在只有少數(shù)幾個并發(fā)操作的時候還無傷大雅,如果需要處理大量的并發(fā)操作時就不合適了。
上一篇:C#實現(xiàn)DataTable轉(zhuǎn)換成IList的方法
欄 目:C#教程
下一篇:當(dāng)用戶退出點擊瀏覽器后退仍可回到原來頁面的解決方案
本文標(biāo)題:解析C#多線程編程中異步多線程的實現(xiàn)及線程池的使用
本文地址:http://mengdiqiu.com.cn/a1/C_jiaocheng/6648.html
您可能感興趣的文章
- 01-10C#停止線程的方法
- 01-10C#實現(xiàn)多線程下載文件的方法
- 01-10C#實現(xiàn)多線程寫入同一個文件的方法
- 01-10C#獲取進程或線程相關(guān)信息的方法
- 01-10C#通過Semaphore類控制線程隊列的方法
- 01-10C#線程隊列用法實例分析
- 01-10C#實現(xiàn)ComboBox控件顯示出多個數(shù)據(jù)源屬性的方法
- 01-10C#中實現(xiàn)一次執(zhí)行多條帶GO的sql語句實例
- 01-10C#類的多態(tài)性詳解
- 01-10C#中Equals方法的常見誤解


閱讀排行
本欄相關(guān)
- 01-10C#通過反射獲取當(dāng)前工程中所有窗體并
- 01-10關(guān)于ASP網(wǎng)頁無法打開的解決方案
- 01-10WinForm限制窗體不能移到屏幕外的方法
- 01-10WinForm繪制圓角的方法
- 01-10C#實現(xiàn)txt定位指定行完整實例
- 01-10WinForm實現(xiàn)仿視頻 器左下角滾動新
- 01-10C#停止線程的方法
- 01-10C#實現(xiàn)清空回收站的方法
- 01-10C#通過重寫Panel改變邊框顏色與寬度的
- 01-10C#實現(xiàn)讀取注冊表監(jiān)控當(dāng)前操作系統(tǒng)已
隨機閱讀
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 01-11ajax實現(xiàn)頁面的局部加載
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 01-10C#中split用法實例總結(jié)
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 01-10delphi制作wav文件的方法
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 04-02jquery與jsp,用jquery
- 01-10使用C語言求解撲克牌的順子及n個骰子