線程 被定義為程序的執(zhí)行路徑。每個(gè)線程都定義了一個(gè)獨(dú)特的控制流。如果您的應(yīng)用程序涉及到復(fù)雜的和耗時(shí)的操作,那么設(shè)置不同的線程執(zhí)行路徑往往是有益的,每個(gè)線程執(zhí)行特定的工作。 線程生命周期線程生命周期開始于 System.Threading.Thread 類的對(duì)象被創(chuàng)建時(shí),結(jié)束于線程被終止或完成執(zhí)行時(shí)。 下面列出了線程生命周期中的各種狀態(tài):
主線程進(jìn)程中第一個(gè)被執(zhí)行的線程稱為主線程。 當(dāng) C# 程序開始執(zhí)行時(shí),主線程自動(dòng)創(chuàng)建。使用 Thread 類創(chuàng)建的線程被主線程的子線程調(diào)用。您可以使用 Thread 類的 CurrentThread 屬性訪問線程。 下面的程序演示了主線程的執(zhí)行: 實(shí)例using
System
; using System.Threading ; namespace MultithreadingApplication { class MainThreadProgram { static void Main ( string [ ] args ) { Thread th = Thread . CurrentThread ; th . Name = 'MainThread' ; Console . WriteLine ( 'This is {0}', th . Name ) ; Console . ReadKey ( ) ; } } } 當(dāng)上面的代碼被編譯和執(zhí)行時(shí),它會(huì)產(chǎn)生下列結(jié)果: This is MainThread 創(chuàng)建線程線程是通過Thread 類創(chuàng)建的。Thread 類調(diào)用 Start() 方法來(lái)開始子線程的執(zhí)行。 下面的程序演示了這個(gè)概念: using System;using System.Threading;namespace MultithreadingApplication{ class ThreadCreationProgram { public static void CallToChildThread() { Console.WriteLine('Child thread starts'); } static void Main(string[] args) { ThreadStart childref = new ThreadStart(CallToChildThread); Console.WriteLine('In Main: Creating the Child thread'); Thread childThread = new Thread(childref); childThread.Start(); Console.ReadKey(); } }} 當(dāng)上面的代碼被編譯和執(zhí)行時(shí),它會(huì)產(chǎn)生下列結(jié)果: In Main: Creating the Child threadChild thread starts 管理線程Thread 類提供了各種管理線程的方法。 下面的實(shí)例演示了 sleep() 方法的使用,用于在一個(gè)特定的時(shí)間暫停線程。 using System;using System.Threading;namespace MultithreadingApplication{ class ThreadCreationProgram { public static void CallToChildThread() { Console.WriteLine('Child thread starts'); // 線程暫停 5000 毫秒 int sleepfor = 5000; Console.WriteLine('Child Thread Paused for {0} seconds', sleepfor / 1000); Thread.Sleep(sleepfor); Console.WriteLine('Child thread resumes'); } static void Main(string[] args) { ThreadStart childref = new ThreadStart(CallToChildThread); Console.WriteLine('In Main: Creating the Child thread'); Thread childThread = new Thread(childref); childThread.Start(); Console.ReadKey(); } }} 當(dāng)上面的代碼被編譯和執(zhí)行時(shí),它會(huì)產(chǎn)生下列結(jié)果: In Main: Creating the Child threadChild thread startsChild Thread Paused for 5 secondsChild thread resumes 銷毀線程Abort() 方法用于銷毀線程。 通過拋出 threadabortexception 在運(yùn)行時(shí)中止線程。這個(gè)異常不能被捕獲,如果有 finally 塊,控制會(huì)被送至 finally 塊。 這個(gè)異常不能被捕獲是什么鬼,可以被捕獲呀 下面的程序說明了這點(diǎn): using System;using System.Threading;namespace MultithreadingApplication{ class ThreadCreationProgram { public static void CallToChildThread() { try { Console.WriteLine('Child thread starts'); // 計(jì)數(shù)到 10 for (int counter = 0; counter <= 10; counter++) { Thread.Sleep(500); Console.WriteLine(counter); } Console.WriteLine('Child Thread Completed'); } catch (ThreadAbortException e) { Console.WriteLine('Thread Abort Exception'); } finally { Console.WriteLine('Couldn't catch the Thread Exception'); } } static void Main(string[] args) { ThreadStart childref = new ThreadStart(CallToChildThread); Console.WriteLine('In Main: Creating the Child thread'); Thread childThread = new Thread(childref); childThread.Start(); // 停止主線程一段時(shí)間 Thread.Sleep(2000); // 現(xiàn)在中止子線程 Console.WriteLine('In Main: Aborting the Child thread'); childThread.Abort(); Console.ReadKey(); } }} 當(dāng)上面的代碼被編譯和執(zhí)行時(shí),它會(huì)產(chǎn)生下列結(jié)果: In Main: Creating the Child threadChild thread starts012In Main: Aborting the Child threadThread Abort ExceptionCouldn't catch the Thread Exception C# 多線程--愛整理一、利用多線程提高程序性能本節(jié)導(dǎo)讀:隨著硬件和網(wǎng)絡(luò)的高速發(fā)展,為多線程(Multithreading)處理并行任務(wù),提供了有利條件。 其實(shí)我們每時(shí)每刻都在享受多線程帶來(lái)的便利,多核處理器多線程工作、Windows操作系統(tǒng)、Web服務(wù)器都在使用多線程工作。 使用多線程直接提高了程序的執(zhí)行效率,因此學(xué)習(xí)多線程對(duì)提高程序運(yùn)行能力非常必要,本節(jié)主要介紹多線程原理及.NET中多線程在.NET面向?qū)ο蟪绦蛟O(shè)計(jì)中的應(yīng)用。 1. 關(guān)于多線程在介紹多線程之前,先了解一下進(jìn)程。 進(jìn)程:獨(dú)立運(yùn)行的程序稱為進(jìn)程。(比如Windows系統(tǒng)后臺(tái)程序,也可以稱為后臺(tái)進(jìn)程) 線程:對(duì)于同一個(gè)程序分為多個(gè)執(zhí)行流,稱為線程。 多線程:使用多個(gè)線程進(jìn)行多任務(wù)處理,稱為多線程。 并發(fā)是針對(duì)于單核處理器而言,但是目前市場(chǎng)上的CPU是多核的(一個(gè)芯片多個(gè)CPU核心),多線程設(shè)計(jì)可以讓多個(gè)任務(wù)分發(fā)到多個(gè)CPU上并行執(zhí)行,可以讓程序更快的執(zhí)行。但是并發(fā)通常是提高運(yùn)行在單核處理器上的程序的性能。這么說感覺有點(diǎn)違背直覺,因?yàn)樵趩魏颂幚砥魃线\(yùn)行并發(fā)程序開銷要比順序執(zhí)行該程序的開銷要大(從上到下順序執(zhí)行程序),因?yàn)椴l(fā)程序中增加了上下文切換的代價(jià)(一個(gè)線程切換到另外一個(gè)線程),從表面上看如果順序執(zhí)行所有程序反而節(jié)省了上下文切換的代價(jià)。 讓這個(gè)問題變得不同的是阻塞,程序中的某個(gè)任務(wù)因?yàn)樵摮绦蚩刂品秶獾囊恍l件(通常是I/O),整個(gè)程序就會(huì)停止下來(lái),直到外部條件發(fā)生變化。此時(shí)多線程的優(yōu)勢(shì)就會(huì)體現(xiàn)出來(lái)了,其他任務(wù)可以通過獲得CPU時(shí)間而繼續(xù)執(zhí)行,而不會(huì)讓整個(gè)程序停下來(lái)。從性能的角度來(lái)看,如果沒有線程阻塞,那么在單核處理器上使用并發(fā)那將毫無(wú)意義。 2. 如何合理使用多線程?A.對(duì)于用戶等待程序處理時(shí),可以使用多線程處理耗時(shí)任務(wù); B.對(duì)于一些不需要即時(shí)完成的任務(wù),可以使用后臺(tái)任務(wù)線程處理; C.對(duì)于多并發(fā)任務(wù),可以使用多線程同時(shí)處理; 這一句的意思是讓并發(fā)線程變成并行線程嗎?即讓原本是單核處理多個(gè)線程,變成多核處理多個(gè)線程(一核分配一個(gè)線程) D.對(duì)于通訊類,比如對(duì)線程阻塞,可以使用多線程。 除過上面的幾個(gè)常用的情況,還有很多情況下可以使用多線程。 3. 多線程的缺點(diǎn)線程自然也有缺點(diǎn),以下列出了一些: A.如果有大量的線程,會(huì)影響性能,因?yàn)椴僮飨到y(tǒng)需要在他們之間切換; B.更多的線程需要更多的內(nèi)存空間; C.線程會(huì)給程序帶來(lái)更多的bug,因此要小心使用,比如:線程任務(wù)在執(zhí)行完成后,要及時(shí)釋放內(nèi)存; D.線程的中止需要考慮其對(duì)程序運(yùn)行的影響。 4. .NET中的兩種多線程.NET本身就是一個(gè)多線程的的環(huán)境。 在.NET中有兩種多線程的: 一種是使用Thread類進(jìn)行線程的創(chuàng)建、啟動(dòng),終止等操作。 一種是使用ThreadPool類用于管理線程池. 5 .NET中使用Thread進(jìn)行多線程處理線性池是一種多線程并發(fā)的處理形式,它就是由一堆已創(chuàng)建好的線程組成。有新任務(wù) -> 取出空閑線程處理任務(wù) -> 任務(wù)處理完成放入線程池等待。避免了處理短時(shí)間任務(wù)時(shí)大量的線程重復(fù)創(chuàng)建、銷毀的代價(jià),非常適用于連續(xù)產(chǎn)生大量并發(fā)任務(wù)的場(chǎng)合。 5.1 Thread類常用方法.NET基礎(chǔ)類庫(kù)的System.Threading命名空間提供了大量的類和接口支持多線程。System.Threading.Thread類是創(chuàng)建并控制線程,設(shè)置其優(yōu)先級(jí)并獲取其狀態(tài)最為常用的類。 下面是該類幾個(gè)至關(guān)重要的方法: Thread.Start():?jiǎn)?dòng)線程的執(zhí)行; Thread.Suspend():掛起線程,或者如果線程已掛起,則不起作用; Thread.Resume():繼續(xù)已掛起的線程; Thread.Interrupt():中止處于Wait或者Sleep或者Join線程狀態(tài)的線程; Thread.Join():阻塞調(diào)用線程,直到某個(gè)線程終止時(shí)為止 Thread.Sleep():將當(dāng)前線程阻塞指定的毫秒數(shù); Thread.Abort():終止此線程。如果線程已經(jīng)在終止,則不能通過Thread.Start()來(lái)啟動(dòng)線程。 suspend 掛起、暫停 resume 繼續(xù)、重新開始 interrupt 中斷、打斷 5.2 Thread類常用屬性Thread的屬性有很多,我們先看最常用的幾個(gè): CurrentThread :用于獲取當(dāng)前線程; ThreadState 當(dāng)前線程的狀態(tài)(5.4介紹); Name:獲取或設(shè)置線程名稱; Priority:獲取或設(shè)置線程的優(yōu)先級(jí)(5.5介紹) ManagedThreadId:獲取當(dāng)前線程的唯一標(biāo)識(shí) IsBackground:獲取或設(shè)置線程是前臺(tái)線程還是后臺(tái)線程(5.6介紹) IsThreadPoolThread:獲取當(dāng)前線程是否是托管線程池(后面章節(jié)會(huì)介紹) 下面創(chuàng)建一個(gè)線程示例,來(lái)說明這幾個(gè)屬性: Thread myThreadTest = new Thread(() =>//這里的new Thread(ThreadStart start)里面ThreadStart是一個(gè)委托,而(()=>{代碼塊...})是lambda表達(dá)式,所以可以說lambda表達(dá)式是基于委托的{ Thread.Sleep(1000); Thread t = Thread.CurrentThread; Console.WriteLine('Name: ' + t.Name); Console.WriteLine('ManagedThreadId: ' + t.ManagedThreadId); Console.WriteLine('State: ' + t.ThreadState); Console.WriteLine('Priority: ' + t.Priority); Console.WriteLine('IsBackground: ' + t.IsBackground); Console.WriteLine('IsThreadPoolThread: ' + t.IsThreadPoolThread);//process 進(jìn)程}){ Name = '線程測(cè)試', Priority = ThreadPriority.Highest};myThreadTest.Start();Console.WriteLine('關(guān)聯(lián)進(jìn)程的運(yùn)行的線程數(shù)量:'+System.Diagnostics.Process.GetCurrentProcess().Threads.Count); 運(yùn)行結(jié)果如下: 我的天竟然有6個(gè)線程,其他四個(gè)線程是? 下面的代碼是一個(gè)小插曲,有助于強(qiáng)化理解線程委托 using System;using System.Threading;namespace MultithreadingApplication{ delegate void P1(object n); delegate void P2(int n1, int n2); class ThreadCreationProgram { public static void MyThreadStart0() { Console.WriteLine('我的線程:0'); } public static void MyThreadStart11(object n) { for (int i = 0; i < (int)n; i++) { Console.WriteLine('我的線程:' + i); } } public static void MyThreadStart12(int n) { for (int i = 0; i < n; i++) { Console.WriteLine('我的線程:' + i); } } public static void MyThreadStart2(int n1, int n2) { for (int i = n1; i < n2; i++) { Console.WriteLine('我的線程:' + i); } } static void Main(string[] args) { //平常情況(自定義委托(參數(shù),返回值情況任意)) P1 p1 = new P1(MyThreadStart11); p1(5); P2 p2 = new P2(MyThreadStart2); p2(2, 5); //線程情況(系統(tǒng)線程定義委托(總共兩個(gè):1無(wú)參無(wú)返回值 2有一個(gè)參無(wú)返回值) ThreadStart ts = new ThreadStart(MyThreadStart0); ts(); ParameterizedThreadStart pts = new ParameterizedThreadStart(MyThreadStart11);//實(shí)例委托pts就相當(dāng)于一個(gè)方法指針,指向一個(gè)方法 pts(5); //我的天在線程中委托的參數(shù)居然是從start()里面?zhèn)鬟M(jìn)去的 new Thread(pts).Start(5); //注意下面兩個(gè)的比較 new Thread((n) => MyThreadStart11(n)).Start(5);//lambda表達(dá)式:(n) => MyThreadStart11(n)就像當(dāng)于委托public delegate void ParameterizedThreadStart(object obj)的一個(gè)實(shí)例,所以傳進(jìn)的參數(shù)n就是object類型,下面的語(yǔ)句需要(int)n進(jìn)行強(qiáng)制轉(zhuǎn)換. new Thread((n) => MyThreadStart12((int)n)).Start(5);//委托的方法參數(shù)類型與委托的參數(shù)數(shù)據(jù)類型不一致,此處卻可以這樣搞,就當(dāng)是封裝的原因吧 new Thread(MyThreadStart0);//甚至可以直接跟個(gè)方法名里面的括號(hào)都省略了 Console.ReadKey(); } }} 5.3 帶參數(shù)的線程方法首先我們寫“簡(jiǎn)單線程”中無(wú)參數(shù)的方法,如下: 注意看注釋 namespace MultithreadingApplication{ class ThreadCreationProgram { static void MyThreadStart() { Console.WriteLine('我是一個(gè)簡(jiǎn)單線程'); } static void Main(string[] args) { //簡(jiǎn)單的線程 Thread myThread = new Thread(MyThreadStart);//此時(shí)的Thread(MyThreadStart)等于Thread(() =>MyThreadStart()),可能是lambda express的簡(jiǎn)寫形式 //那么也就是說在無(wú)參方法調(diào)用委托時(shí)可以不用先實(shí)例化一個(gè)委托ThreadStart ts = new ThreadStart(MyThreadStart)然后再將ts傳進(jìn)線程Thread myThread = new Thread(ts),而可以直接在線程里傳此方法Thread myThread = new Thread(MyThreadStart),這樣的話就簡(jiǎn)單一些了 myThread.Start(); } }} 我們使用Lambda表達(dá)式來(lái)改寫前面“簡(jiǎn)單線程”中無(wú)參數(shù)的方法,如下: namespace MultithreadingApplication{ class ThreadCreationProgram { static void Main(string[] args) { new Thread(() => { //此處可以說用無(wú)參的方法調(diào)用委托 for (int i = 0; i < 5; i++) Console.WriteLine('我的線程一-[{0}]', i); }).Start(); Console.ReadKey(); } }} 上面示例創(chuàng)建的線程并沒有帶參數(shù),如果是一個(gè)有參數(shù)的方法,線程該如何創(chuàng)建? 別擔(dān)心,.NET為我們提供了一個(gè)ParameterizedThreadStart 委托 來(lái)解決帶一個(gè)參數(shù)的問題,如下: new Thread((num) =>{ for (int i = 0; i < (int)num; i++) Console.WriteLine('我的線程二--[{0}]', i);}).Start(5);/*由于ParameterizedThreadStart 委托傳的值是object類型的,所以要強(qiáng)制轉(zhuǎn)化一下*/ 運(yùn)行結(jié)果如下: 那么問題來(lái)了,ParameterizedThreadStart委托只有一個(gè)包含數(shù)據(jù)的參數(shù), 對(duì)于多個(gè)參數(shù)呢?我們可以使用一個(gè)無(wú)參數(shù)的方法來(lái)包裝它,如下: 先創(chuàng)建一個(gè)帶參數(shù)的方法: static void myThreadStart(int numA, int numB){ for (int i = (int)numA; i < (int)numB; i++) Console.WriteLine('我的線程三---[{0}]', i);} 然后通過無(wú)參數(shù)的委托來(lái)包裝它,如下 : //這里默認(rèn)匹配是public delegate void ThreadStart();的一個(gè)實(shí)例new Thread(() => myThreadStart(0, 5)).Start();//事到如今我感覺可以肯定的說lambda表達(dá)式實(shí)質(zhì)上就是一個(gè)委托 運(yùn)行結(jié)果如下: 5.4 Thread狀態(tài)我們對(duì)于線程啟動(dòng)以后,如何進(jìn)行掛起和終止、重新啟用,首先線程在運(yùn)行后有一個(gè)狀態(tài)。 System.Threading.Thread.ThreadState屬性定義了執(zhí)行時(shí)線程的狀態(tài)。線程從創(chuàng)建到線程終止,它一定處于其中某一個(gè)狀態(tài)。 A.Unstarted:當(dāng)線程被創(chuàng)建時(shí),它處在Unstarted狀態(tài)。 B.Running:Thread類的Start() 方法將使線程狀態(tài)變?yōu)镽unning狀態(tài),線程將一直處于這樣的狀態(tài),除非我們調(diào)用了相應(yīng)的方法使其掛起、阻塞、銷毀或者自然終止。 C.Suspended:如果線程被掛起,它將處于Suspended狀態(tài)。 D.Running:我們調(diào)用Resume()方法使其重新執(zhí)行,這時(shí)候線程將重新變?yōu)镽unning狀態(tài)。 E.Stopped:一旦線程被銷毀或者終止,線程處于Stopped狀態(tài)。處于這個(gè)狀態(tài)的線程將不復(fù)存在,正如線程開始啟動(dòng),線程將不可能回到Unstarted狀態(tài)。 F.Background:線程還有一個(gè)Background狀態(tài),它表明線程運(yùn)行在前臺(tái)還是后臺(tái)。在一個(gè)確定的時(shí)間,線程可能處于多個(gè)狀態(tài)。 G.WaitSleepJoin、AbortRequested:舉例子來(lái)說,一個(gè)線程被調(diào)用了Sleep而處于阻塞,而接著另外一個(gè)線程調(diào)用Abort方法于這個(gè)阻塞的線程,這時(shí)候線程將同時(shí)處于WaitSleepJoin和AbortRequested狀態(tài)。 H.一旦線程響應(yīng)轉(zhuǎn)為Sleep阻塞或者中止,當(dāng)銷毀時(shí)會(huì)拋出ThreadAbortException異常。 ThreadState枚舉的10種執(zhí)行狀態(tài)如下: 上圖了解一個(gè)WaitSleepJoin就可以了 monitor 監(jiān)視器,監(jiān)聽器,監(jiān)控器 對(duì)于線程阻塞和同步問題,將在下一節(jié)繼續(xù)介紹。 5.5. 線程優(yōu)先級(jí)對(duì)于多線程任務(wù),我們可以根據(jù)其重要性和運(yùn)行所需要的資源情況,設(shè)置他的優(yōu)先級(jí) System.Threading.ThreadPriority枚舉了線程的優(yōu)先級(jí)別,從而決定了線程能夠得到多少CPU時(shí)間。 高優(yōu)先級(jí)的線程通常會(huì)比一般優(yōu)先級(jí)的線程得到更多的CPU時(shí)間 主線程與各種線程(高優(yōu)先級(jí),低優(yōu)先級(jí)線程)搶奪cup資源,一般優(yōu)先級(jí)越高搶到cup資源的概率越高,從而cpu占用的時(shí)間越多. 而不是每個(gè)線程一個(gè)一個(gè)排序來(lái),排到高優(yōu)先級(jí)線程時(shí)cup分配的時(shí)間多一些,排到低優(yōu)先級(jí)線程時(shí)cup分配的時(shí)間少一些, 新創(chuàng)建的線程優(yōu)先級(jí)為一般優(yōu)先級(jí),我們可以設(shè)置線程的優(yōu)先級(jí)別的值,如下面所示: 線程搶占cpu資源可以用如下代碼測(cè)試: static void Main(string[] args){ int numberH1 = 0,numberH2=0, numberL1 = 0, numberL2=0; bool state = true; new Thread(() => { while (state) { numberH1++; Console.WriteLine('H1'); }; }) { Priority = ThreadPriority.Highest, Name = '線程A' }.Start(); new Thread(() => { while (state) { numberH2++; Console.WriteLine('H2'); }; }) { Priority = ThreadPriority.Highest, Name = '線程A' }.Start(); new Thread(() => { while (state) { numberL1++; Console.WriteLine('L1'); }; }) { Priority = ThreadPriority.Lowest, Name = '線程B' }.Start(); //讓主線程掛件1秒 Thread.Sleep(1000); state = false; Console.WriteLine('線程H1: {0}, 線程H2: {1}, 線程L1: {2}', numberH1,numberH2,numberL1); Console.ReadKey();} 5.6 前臺(tái)線程和后臺(tái)線程線程有兩種,默認(rèn)情況下為前臺(tái)線程,要想設(shè)置為后臺(tái)線程也非常容易,只需要加一個(gè)屬性:thread.IsBackground = true;就可以變?yōu)橐粋€(gè)后臺(tái)線程了。 重點(diǎn)來(lái)了,前后臺(tái)線程的區(qū)別: A.前臺(tái)線程:應(yīng)用程序必須執(zhí)行完所有的前臺(tái)線程才能退出; B.后臺(tái)線程:應(yīng)用程序不必考慮其是否全部完成,可以直接退出。應(yīng)用程序退出時(shí),自動(dòng)終止后臺(tái)線程。 下面我們使用一個(gè)輸出從0到1000的數(shù)字,來(lái)實(shí)驗(yàn)一下前臺(tái)線程和后臺(tái)線程的區(qū)別: static void Main(string[] args){ Thread myThread = new Thread(() => { for (int i = 0; i < 1000; i++) Console.WriteLine(i); }); var key = Console.ReadLine(); if (key == '1') { myThread.IsBackground = true; myThread.Start(); } else { myThread.IsBackground = false; myThread.Start(); }} 如果輸入1(后臺(tái)線程),線程會(huì)很快關(guān)閉,并不會(huì)等輸出完1000個(gè)數(shù)字再關(guān)閉; 如果輸入其它(前臺(tái)線程),回車后,則線程會(huì)等1000個(gè)數(shù)字輸出完后,窗口關(guān)閉; 6. 本節(jié)要點(diǎn):A.本節(jié)主要介紹了線程的基本知識(shí); B.Thread常用的屬性、方法; C.Thread委托的方法有多個(gè)參數(shù)的用法; D.Thread的優(yōu)先級(jí); E.Thread的執(zhí)行狀態(tài); F.前臺(tái)線程和后臺(tái)線程; 后面會(huì)繼續(xù)深入介紹利用線程提高程序性能。 二、多線程高級(jí)應(yīng)用本節(jié)要點(diǎn):上節(jié)介紹了多線程的基本使用方法和基本應(yīng)用示例,本節(jié)深入介紹.NET多線程中的高級(jí)應(yīng)用。 主要有在線程資源共享中的線程安全和線程沖突的解決方案;多線程同步,使用線程鎖和線程通知實(shí)現(xiàn)線程同步。 1、 ThreadStatic特性特性:[ThreadStatic] 功能:指定靜態(tài)字段在不同線程中擁有不同的值 在此之前,我們先看一個(gè)多線程的示例: 我們定義一個(gè)靜態(tài)字段: static int num = 0;new Thread(() =>{ for (int i = 0; i < 1000000; i++) ++num; Console.WriteLine('來(lái)自{0}:{1}', Thread.CurrentThread.Name, num);}){ Name = '線程一' }.Start();隱藏代碼new Thread(() =>{ for (int i = 0; i < 2000000; i++) ++num; Console.WriteLine('來(lái)自{0}:{1}', Thread.CurrentThread.Name, num);}){ Name = '線程二' }.Start(); 運(yùn)行多次結(jié)果如下:
可以看到,三次的運(yùn)行結(jié)果均不相同,產(chǎn)生這種問題的原因是多線程中同步共享問題導(dǎo)致的,即是多個(gè)線程同時(shí)共享了一個(gè)資源。 此處代碼與上下文無(wú)關(guān),知識(shí)兩個(gè)疑惑,注意看注釋 using System;using System.Threading;namespace MultithreadingApplication{ class ThreadCreationProgram { static int num = 0; static void Main(string[] args) { new Thread(() => { int k=0; for (k = 0; k < 100000; k++) ++num; Console.WriteLine('來(lái)自{0}:{1} 此時(shí)k的值為{2}', Thread.CurrentThread.Name, num,k); }) { Name = '線程一' }.Start(); new Thread(() => { int j = 0; for (; j < 200000; j++) ++num; Console.WriteLine('來(lái)自{0}:{1} 此時(shí)j的值為{2}', Thread.CurrentThread.Name, num,j); }) { Name = '線程二' }.Start(); Thread.Sleep(5*1000); Console.WriteLine('主線程又開始'); Console.ReadKey(); } } //疑惑 1 兩個(gè)線程執(zhí)行次數(shù)竟然大于3000000,也竟然有小于3000000的 //2 竟然會(huì)輸出:來(lái)自線程一:56265 答;這是因?yàn)椴粌H只有兩個(gè)線程在執(zhí)行,還有個(gè)主線程在執(zhí)行,不要忽略了. //因?yàn)橹骶€程走到了Console.ReadKey(),所以會(huì)在控制臺(tái)輸出線程一還未走完的num值,此時(shí)num值也就小于1000000了 //那么為了避免主線程對(duì)子線程的影響可以阻塞主線程一段時(shí)間知道子線程完成(用sleep方法)--我的天吶我發(fā)現(xiàn)排除了主線程readkey的干擾后 //仍然會(huì)輸出:來(lái)自線程一:989265的情況者,這發(fā)生了什么?} 如何解決上述問題,最簡(jiǎn)單的方法就是使用靜態(tài)字段的ThreadStatic特性。 在定義靜態(tài)字段時(shí),加上[ThreadStatic]特性,如下: [ThreadStatic]static int num = 0; 兩個(gè)線程不變的情況下,再次運(yùn)行,結(jié)果如下: 不論運(yùn)行多少次,結(jié)果都是一樣的,當(dāng)字段被ThreadStatic特性修飾后,它的值在每個(gè)線程中都是不同的,即每個(gè)線程對(duì)static字段都會(huì)重新分配內(nèi)存空間,就當(dāng)然于一次new操作,這樣一來(lái),由于static字段所產(chǎn)生的問題也就沒有了。 2. 資源共享多線程的資源共享,也就是多線程同步(即資源同步),需要注意的是線程同步指的是線程所訪問的資源同步,并非是線程本身的同步。 在實(shí)際使用多線程的過程中,并非都是各個(gè)線程訪問不同的資源。 下面看一個(gè)線程示例,假如我們并不知道線程要多久完成,我們等待一個(gè)固定的時(shí)間(假如是500毫秒): 先定義一個(gè)靜態(tài)字段: static int result;Thread myThread = new Thread(() =>{ Thread.Sleep(1000); result = 100;});myThread.Start();Thread.Sleep(500); Console.WriteLine(result); 運(yùn)行結(jié)果如下: 可以看到結(jié)果是0,顯然不是我們想要的,但往往在線程執(zhí)行過程中,我們并不知道它要多久完成,能不能在線程完成后有一個(gè)通知? 下面的代碼與上下文無(wú)關(guān),只是一個(gè)小注意點(diǎn) using System;using System.Threading;namespace MultithreadingApplication{ class ThreadCreationProgram { static int result; static void Main(string[] args) { Thread myThread = new Thread(() => { Thread.Sleep(10000); result = 100; Console.WriteLine(result); Console.ReadKey(); }); myThread.Start();//這一步再往下走兩個(gè)線程就開始搶奪cup資源了 Thread.Sleep(1000); Console.WriteLine(result); Console.ReadKey();//執(zhí)行完這一步,并不會(huì)就一直停在這里,當(dāng)myThread線程睡眠時(shí)間到了,會(huì)自動(dòng)執(zhí)行myThread線程 //然后停在myThread的Readkey處,在控制臺(tái)輸入任意值,走到24,再輸入任意值,走到18. } }} .NET為我們提供了一個(gè)Join方法,就是線程阻塞,可以解決上述問題,我們使用Stopwatch來(lái)記時(shí) 改進(jìn)線程代碼如下: using System;using System.Threading;namespace MultithreadingApplication{ class ThreadCreationProgram { static int result; static void Main(string[] args) { //Diagnostic 診斷. Stopwatch 跑表 //StartNew()初始化新的 System.Diagnostics.Stopwatch 實(shí)例,將運(yùn)行時(shí)間屬性設(shè)置為零,然后開始測(cè)量運(yùn)行時(shí)間。 System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew(); Thread myThread = new Thread(() => { Thread.Sleep(1000); result = 100; }); myThread.Start(); Thread.Sleep(500);//走到這一步主線程睡眠,進(jìn)入子線程myThread myThread.Join();//0.5秒鐘后回到主線程這一步,走到這一步時(shí)會(huì)停下來(lái)直到子線程myThread執(zhí)行完畢. Console.WriteLine(watch.ElapsedMilliseconds);//Elapsed 消逝、過去 Millisecond 毫秒 Console.WriteLine(result); Console.ReadKey(); } }}//Join()和sleep()都是線程阻塞 運(yùn)行結(jié)果如下: 結(jié)果和我們想要的是一致的。 3. 線程鎖除了上面示例的方法,對(duì)于線程同步,.NET還為我們提供了一個(gè)鎖機(jī)制來(lái)解決同步,再次改進(jìn)上面示例如下: using System;using System.Threading;namespace MultithreadingApplication{ class ThreadCreationProgram { //先定義一個(gè)靜態(tài)字段來(lái)存儲(chǔ)鎖 static object locker = new object(); static int result; static void Main(string[] args) { System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew(); Thread t1 = new Thread(() => { lock (locker)//lock (x)里面的x是引用類型 { Thread.Sleep(10000); result = 100; } }); t1.Start(); Thread.Sleep(5000); lock (locker)//lock(x)中的x是同一個(gè)引用類型的變量時(shí),這些鎖之間是互斥的,只有最先執(zhí)行的鎖執(zhí)行完,才會(huì)執(zhí)行下一個(gè)鎖 { Console.WriteLine('線程耗時(shí):' + watch.ElapsedMilliseconds); Console.WriteLine('線程輸出:' + result); } Console.ReadKey(); } }} 運(yùn)行結(jié)果如下: 運(yùn)行結(jié)果和上面示例一樣,如果線程處理過程較復(fù)雜,可以看到耗時(shí)明顯減少,這是一種用比阻塞更效率的方式完成線程同步。 4. 線程通知前面說到了能否在一個(gè)線程完成后,通知等待的線程呢,這里.NET為我們提供了一個(gè)事件通知的方法來(lái)解決這個(gè)問題。 4.1 AutoResetEvent改進(jìn)上面的線程如下: using System;using System.Threading;//一個(gè)線程完成后通知另外一個(gè)線程(是一個(gè)!與下面的幾個(gè)不同)namespace MultithreadingApplication{ class ThreadCreationProgram { //先定義一個(gè)通知對(duì)象 //EventWaitHandle 表示一個(gè)線程同步事件。 static EventWaitHandle tellMe = new AutoResetEvent(false);//里面的boolean該值指示是否將初始狀態(tài)設(shè)置為終止?fàn)顟B(tài)的類。 static int result = 0; static void Main(string[] args) { System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew(); Thread myThread = new Thread(() => { Thread.Sleep(5000); result = 100; tellMe.Set();//將事件狀態(tài)設(shè)置為有信號(hào),從而允許一個(gè)或多個(gè)等待線程繼續(xù)執(zhí)行。 }); myThread.Start(); tellMe.WaitOne();//阻止當(dāng)前線程,直到當(dāng)前 System.Threading.WaitHandle 收到信號(hào)。 Console.WriteLine('線程耗時(shí):' + watch.ElapsedMilliseconds); Console.WriteLine('線程輸出:' + result); } }//待在同一個(gè)代碼塊的兩個(gè)線程是資源共享的,即兩個(gè)線程是同步的} 運(yùn)行結(jié)果如下: 4.2 ManualResetEvent和AutoResetEvent 相對(duì)的還有一個(gè) ManualResetEvent 手動(dòng)模式,他們的區(qū)別在于,在線程結(jié)束后ManualResetEvent 還是可以通行的,除非手動(dòng)Reset關(guān)閉。下面看一個(gè)示例: 這句話的意思是在mre.Set()和mre.WaitOne()執(zhí)行完之后,如果有另一個(gè)mre.WaitOne(),此時(shí)仍可以通過.如果是AutoResetEvent的話就不可以了,可以將下面的代碼ManualResetEvent改成AutoResetEvent試一下 using System;using System.Threading;namespace MultithreadingApplication{ //一個(gè)線程完成後通知其他個(gè)線程.(其他的意思是多于一個(gè)) class ThreadCreationProgram { //EventWaitHandle 表示一個(gè)線程同步事件。 static EventWaitHandle mre = new ManualResetEvent(false);//布爾值指示是否將初始狀態(tài)設(shè)置為終止?fàn)顟B(tài)的類。 static int result = 0; static void Main(string[] args) { System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew(); Thread myThreadFirst = new Thread(() => { Thread.Sleep(10000); result = 100; mre.Set();//將事件狀態(tài)設(shè)置為有信號(hào),從而允許一個(gè)或多個(gè)等待線程繼續(xù)執(zhí)行。 }) { Name = '線程一' }; Thread myThreadSecond = new Thread(() => { 兩個(gè)WaitOne()執(zhí)行后進(jìn)入線程一執(zhí)行Set(),Set執(zhí)行后代表兩個(gè)WaitOne都已經(jīng)通過 mre.WaitOne(); Console.WriteLine(Thread.CurrentThread.Name + '獲取結(jié)果:' + result + '(' + System.DateTime.Now.ToString() + ')'); }) { Name = '線程二' }; myThreadFirst.Start(); myThreadSecond.Start(); mre.WaitOne();//阻止當(dāng)前線程,直到當(dāng)前 System.Threading.WaitHandle 收到信號(hào)。 Console.WriteLine('線程耗時(shí):' + watch.ElapsedMilliseconds + '(' + System.DateTime.Now.ToString() + ')'); Console.WriteLine('線程輸出:' + result + '(' + System.DateTime.Now.ToString() + ')'); Console.ReadKey(); } }//手動(dòng)Reset關(guān)閉,mre.Reset();} 運(yùn)行結(jié)果如下: 線程二獲取結(jié)果:100可能先輸出,或者在中間輸出,也可能最后輸出,這取決于主線程與線程二對(duì)cpu資源的搶奪 下面代碼是手動(dòng) Reset()關(guān)閉展示 using System;using System.Threading;namespace MultithreadingApplication{ //一個(gè)線程完成後通知其他個(gè)線程.(其他的意思是多于一個(gè)) class ThreadCreationProgram { //EventWaitHandle 表示一個(gè)線程同步事件。 static EventWaitHandle mre = new ManualResetEvent(false);//布爾值指示是否將初始狀態(tài)設(shè)置為終止?fàn)顟B(tài)的類。 static int result = 0; static void Main(string[] args) { System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew(); Thread myThreadFirst = new Thread(() => { Thread.Sleep(1000); result = 100; mre.Set();//將事件狀態(tài)設(shè)置為有信號(hào),從而允許一個(gè)或多個(gè)等待線程繼續(xù)執(zhí)行。 }) { Name = '線程一' }; Thread myThreadSecond = new Thread(() => { mre.WaitOne();//兩個(gè)WaitOne()同時(shí)執(zhí)行后進(jìn)入線程一執(zhí)行Set(),Set執(zhí)行后代表兩個(gè)WaitOne都已經(jīng)通過 Console.WriteLine(Thread.CurrentThread.Name + '獲取結(jié)果:' + result + '(' + System.DateTime.Now.ToString() + ')'); }) { Name = '線程二' }; Thread myThreadThird = new Thread(() => { mre.Reset(); mre.WaitOne(); Console.WriteLine(Thread.CurrentThread.Name + '獲取結(jié)果:' + result + '(' + System.DateTime.Now.ToString() + ')'); }) { Name = '線程三' }; myThreadFirst.Start(); myThreadSecond.Start(); mre.WaitOne();//阻止當(dāng)前線程,直到當(dāng)前 System.Threading.WaitHandle 收到信號(hào)。 Console.WriteLine('線程耗時(shí):' + watch.ElapsedMilliseconds + '(' + System.DateTime.Now.ToString() + ')'); Console.WriteLine('線程輸出:' + result + '(' + System.DateTime.Now.ToString() + ')'); myThreadThird.Start(); Thread.Sleep(1000); mre.Set();//將這一句注釋掉線程三WaitOne()就等不到信號(hào),從而會(huì)被一直阻塞. Console.ReadKey(); } }} 4.3. SemaphoreSemaphore也是線程通知的一種,上面的通知模式,在線程開啟的數(shù)量很多的情況下,使用Reset()關(guān)閉時(shí),如果不使用Sleep休眠一下,很有可能導(dǎo)致某些線程沒有恢復(fù)的情況下,某一線程提前關(guān)閉,對(duì)于這種很難預(yù)測(cè)的情況,.NET提供了更高級(jí)的通知方式Semaphore,可以保證在超多線程時(shí)不會(huì)出現(xiàn)上述問題。 using System;using System.Threading;//semaphor 發(fā)信號(hào),打旗語(yǔ)namespace MultithreadingApplication{ class Program { //先定義一個(gè)通知對(duì)象的靜態(tài)字段 //Semaphore(初始授予1個(gè)請(qǐng)求數(shù),設(shè)置最大可授予5個(gè)請(qǐng)求數(shù)) static Semaphore semaphore = new Semaphore(1, 5);//初始授予1個(gè)請(qǐng)求數(shù),如果沒有semaphore.Release()語(yǔ)句,則只會(huì)執(zhí)行一個(gè)子線程,執(zhí)行完之后請(qǐng)求數(shù)又會(huì)變成0 static void Main(string[] args) { for (int i = 1; i <= 5; i++) { Thread thread = new Thread(Work); thread.Start(i); } Thread.Sleep(2000); //授予3個(gè)請(qǐng)求 semaphore.Release(3); Console.ReadLine(); } static void Work(object obj) { semaphore.WaitOne(); Console.WriteLine('print: {0}', obj); } }//程序執(zhí)行完畢會(huì)輸出四個(gè)記錄} 5. 本節(jié)要點(diǎn):A.線程中靜態(tài)字段的ThreadStatic特性,使用該字段在不同線程中擁有不同的值 B.線程同步的幾種方式,線程鎖和線程通知 C.線程通知的兩種方式:AutoResetEvent /ManualResetEvent 和 Semaphore 多線程的更多特性,下一節(jié)繼續(xù)深入介紹。 三、利用多線程提高程序性能(下)本節(jié)導(dǎo)讀:上節(jié)說了線程同步中使用線程鎖和線程通知的方式來(lái)處理資源共享問題,這些是多線程的基本原理。 .NET 4.0 以后對(duì)多線程的實(shí)現(xiàn)變得更簡(jiǎn)單了。 本節(jié)主要討論 .NET4.0 多線程的新特性——使用 Task類創(chuàng)建多線程。 讀前必備:A. LINQ使用 [.net 面向?qū)ο缶幊袒A(chǔ)] (20) LINQ使用 B. 泛型 [.net 面向?qū)ο缶幊袒A(chǔ)] (18) 泛型 1.線程池ThreadPool在介紹4.0以后的多線程新特征之前,先簡(jiǎn)單說一下線程池。 通過前面對(duì)多線程的學(xué)習(xí),我們發(fā)現(xiàn)多線程的創(chuàng)建和使用并不難,難的在于多線程的管理,特別是線程數(shù)量級(jí)很多的情況下,如何進(jìn)行管理和資源釋放。需要使用線程池來(lái)解決。 簡(jiǎn)單來(lái)說線程池就是.NET提供的存放線程的一個(gè)對(duì)象容器。 為什么要使用線性池 微軟官網(wǎng)說法如下:許多應(yīng)用程序創(chuàng)建大量處于睡眠狀態(tài),等待事件發(fā)生的線程。還有許多線程可能會(huì)進(jìn)入休眠狀態(tài),這些線程只是為了定期喚醒以輪詢更改或更新的狀態(tài)信息。 線程池,使您可以通過由系統(tǒng)管理的工作線程池來(lái)更有效地使用線程。 說得簡(jiǎn)單一點(diǎn),每新建一個(gè)線程都需要占用內(nèi)存空間和其他資源,而新建了那么多線程,有很多在休眠,或者在等待資源釋放;又有許多線程只是周期性的做一些小工作,如刷新數(shù)據(jù)等等,太浪費(fèi)了,劃不來(lái),實(shí)際編程中大量線程突發(fā),然后在短時(shí)間內(nèi)結(jié)束的情況很少見。于是,就提出了線程池的概念。線程池中的線程執(zhí)行完指定的方法后并不會(huì)自動(dòng)消除,而是以掛起狀態(tài)返回線程池,如果應(yīng)用程序再次向線程池發(fā)出請(qǐng)求,那么處以掛起狀態(tài)的線程就會(huì)被激活并執(zhí)行任務(wù),而不會(huì)創(chuàng)建新線程,這就節(jié)約了很多開銷。只有當(dāng)線程數(shù)達(dá)到最大線程數(shù)量,系統(tǒng)才會(huì)自動(dòng)銷毀線程。因此,使用線程池可以避免大量的創(chuàng)建和銷毀的開支,具有更好的性能和穩(wěn)定性,其次,開發(fā)人員把線程交給系統(tǒng)管理,可以集中精力處理其他任務(wù)。 線程池線程分為兩類:工作線程和 IO 線程 . 線程池是一種多線程處理形式,處理過程中將任務(wù)添加到隊(duì)列,然后在創(chuàng)建線程后自動(dòng)啟動(dòng)這些任務(wù)。 下面是一個(gè)線程池的示例: using System;using System.Threading;namespace MultithreadingApplication{ class Program { //先設(shè)置一個(gè)創(chuàng)建線程總數(shù)靜態(tài)字段: static readonly int totalThreads = 20; static void Main(string[] args) { //線性池是靜態(tài)類可以直接使用 //參數(shù)1:要由線程池根據(jù)需要?jiǎng)?chuàng)建的新的最小工作程序線程數(shù)。 //參數(shù)2:要由線程池根據(jù)需要?jiǎng)?chuàng)建的新的最小空閑異步 I/O 線程數(shù)。 ThreadPool.SetMinThreads(2, 2); //參數(shù)1:線程池中輔助線程的最大數(shù)目。 //參數(shù)2:線程池中異步 I/O 線程的最大數(shù)目。 ThreadPool.SetMaxThreads(20, 20); for (int i = 0; i < totalThreads; i++) { ThreadPool.QueueUserWorkItem(o => { Thread.Sleep(1000); int a, b; //參數(shù)1:可用輔助線程的數(shù)目。 //參數(shù)2:可用異步 I/O 線程的數(shù)目。 ThreadPool.GetAvailableThreads(out a, out b); Console.WriteLine(string.Format('({0}/{1}) #{2} : {3}', a, b, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString())); }); } Console.WriteLine('主線程完成'); Console.ReadKey(); } }} 2. Task類用 ThreadPool 的 QueueUserWorkItem() 方法發(fā)起一次異步的線程執(zhí)行很簡(jiǎn)單,但是該方法最大的問題是沒有一個(gè)內(nèi)建的機(jī)制讓你知道操作什么時(shí)候完成,有沒有一個(gè)內(nèi)建的機(jī)制在操作完成后獲得一個(gè)返回值。為此, 在.NET 4.0 以后,我們 可以使用 System.Threading.Tasks 中的 Task 類。 這也是.NET 4.0 以后多線程的推薦做法。 構(gòu)造一個(gè) Task<T> 對(duì)象,并為泛型 T 參數(shù)傳遞一個(gè)操作的返回類型。 Task類可以使用多種方法創(chuàng)建多線程,下面詳細(xì)介紹。 2.1 使用Factory屬性Task 實(shí)例可以用各種不同的方式創(chuàng)建。 最常見的方法是使用任務(wù)的 Factory 屬性檢索可用來(lái)創(chuàng)建用于多個(gè)用途的 TaskFactory 實(shí)例。 例如,要?jiǎng)?chuàng)建運(yùn)行操作的 Task ,可以使用工廠的 StartNew 方法: //最簡(jiǎn)單的線程示例Task.Factory.StartNew(() =>{ Console.WriteLine('我是使用Factory屬性創(chuàng)建的線程');}); 如果想簡(jiǎn)單的創(chuàng)建一個(gè)Task,那么使用Factory.StartNew()來(lái)創(chuàng)建,很簡(jiǎn)便。 如果像對(duì)所創(chuàng)建的Task附加更多的定制和設(shè)置特定的屬性,請(qǐng)繼續(xù)往下看。 2.2 使用Task實(shí)例實(shí)現(xiàn)多線程//簡(jiǎn)單的Task實(shí)例創(chuàng)建線程Action<object> action = (object obj) =>{ Console.WriteLine('Task={0}, obj={1}, Thread={2}', Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);};//上面的是簡(jiǎn)寫形式,也可以寫成下面的形式.//Action<object> action = new Action<object>((object obj) =>//{// Console.WriteLine('Task={0}, obj={1}, Thread={2}', Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);//});Task t1 = new Task(action, '參數(shù)');t1.Start(); 運(yùn)行結(jié)果如下: //簡(jiǎn)寫上面實(shí)例,并創(chuàng)建100個(gè)線程System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();int m = 100;Task[] tasks = new Task[m];for (int i = 0; i < m; i++){ tasks[i] = new Task((object obj) => { Thread.Sleep(200); Console.WriteLine('Task={0}, obj={1}, Thread={2},當(dāng)前時(shí)間:{3}', Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId, System.DateTime.Now.ToString()); }, '參數(shù)' + i.ToString() //public Task(Action<object> action, object state); ); tasks[i].Start();//線程開始} Task.WaitAll(tasks); //等待提供的所有 System.Threading.Tasks.Task 對(duì)象完成執(zhí)行過程。Console.WriteLine('線程耗時(shí):{0},當(dāng)前時(shí)間:{1}' ,watch.ElapsedMilliseconds,System.DateTime.Now.ToString()); 這里task創(chuàng)建的100個(gè)線程貌似是異步執(zhí)行的 運(yùn)行結(jié)果如下: 2.3 Task傳入?yún)?shù)上面介紹了使用一個(gè)Action委托來(lái)完成線程,那么給線程中傳入?yún)?shù),就可以使用System.Action<object>來(lái)完成。 傳入一個(gè)參數(shù)的示例: /// <summary>/// 一個(gè)參數(shù)的方法/// </summary>/// <param name='parameter'></param>static void MyMethod(string parameter){ Console.WriteLine('{0}', parameter);} 調(diào)用如下: //Task傳入一個(gè)參數(shù)Task myTask = new Task((parameter) => MyMethod(parameter.ToString()), 'aaa');myTask.Start(); 傳入多個(gè)參數(shù)如下: /// <summary>/// 多個(gè)參數(shù)的方法/// </summary>/// <param name='parameter1'></param>/// <param name='parameter2'></param>/// <param name='parameter3'></param>static void MyMethod(string parameter1,int parameter2,DateTime parameter3){ Console.WriteLine('{0} {1} {2}', parameter1,parameter2.ToString(),parameter3.ToString());} 調(diào)用如下: //Task傳入多個(gè)參數(shù)for (int i = 1; i <= 20; i++){ new Task(() => { MyMethod('我的線程', i, DateTime.Now); }).Start(); Thread.Sleep(200);} 運(yùn)行結(jié)果如下: 對(duì)于傳入多個(gè)參數(shù),可以使用無(wú)參數(shù)委托包裝一個(gè)多參數(shù)的方法來(lái)完成。 2.4 Task的結(jié)果要獲取Task的結(jié)果,在創(chuàng)建Task的時(shí)候,就要采用Task<T>來(lái)實(shí)例化一個(gè)Task。 其中的T就是Task執(zhí)行完成之后返回結(jié)果的類型。 通過Task實(shí)例的Result屬性就可以獲取結(jié)果。 System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();Task<int> myTask = new Task<int>(() =>//這里面泛型委托修飾符out表示協(xié)變{ int sum = 0; for (int i = 0; i < 10000; i++) sum += i; return sum;});myTask.Start(); Console.WriteLine('結(jié)果: {0} 耗時(shí):{1}', myTask.Result,watch.ElapsedMilliseconds); 這里task創(chuàng)建線程執(zhí)行完才會(huì)繼續(xù)執(zhí)行主線程 運(yùn)行結(jié)果如下: 使用Factory屬性來(lái)完成上面的示例: //使用Factory屬性創(chuàng)建System.Diagnostics.Stopwatch watchSecond = System.Diagnostics.Stopwatch.StartNew();Task<int> myTaskSecond = Task.Factory.StartNew<int>(() =>{ int sum = 0; for (int i = 0; i < 10000; i++) sum += i; return sum;}); Console.WriteLine('結(jié)果: {0} 耗時(shí):{1}', myTaskSecond.Result, watchSecond.ElapsedMilliseconds); 這里task創(chuàng)建線程執(zhí)行完才會(huì)繼續(xù)執(zhí)行主線程 運(yùn)行結(jié)果如下: 多線程除以上的一些基礎(chǔ)知識(shí),在處理各種并行任務(wù)和多核編程中的使用,小伙伴可以參考專門關(guān)于多線程的書籍學(xué)習(xí)。 想要完全深入的學(xué)習(xí)多線程需要慢慢修煉,不斷積累。 3. 本節(jié)要點(diǎn):A.本點(diǎn)簡(jiǎn)單介紹了線程池ThreadPool的使用; B.介紹一使用Task進(jìn)行多線程創(chuàng)建及Tast的參數(shù)傳入和返回結(jié)果。 線程一些小知識(shí)點(diǎn) 并發(fā)與并行例子:當(dāng)你要吃飯又要玩游戲 順序執(zhí)行:先吃完飯?jiān)偻嬗螒?br> 并發(fā):吃口飯玩一會(huì)游戲 并行:邊吃飯邊玩游戲 異步 同步 Thread ThreadPool和Task ThreadPool是Thread基礎(chǔ)上的一個(gè)線程池,目的是減少頻繁創(chuàng)建線程的開銷。線程很貴,要開新的stack,要增加CPU上下文切換,所以ThreadPool適合頻繁、短期執(zhí)行的小操作。調(diào)度算法是自適應(yīng)的,會(huì)根據(jù)程序執(zhí)行的模式調(diào)整配置,通常不需要自己調(diào)度線程。另外分為Worker和IO兩個(gè)池。 Task或者說TPL是一個(gè)更上層的封裝,NB之處在于continuation。continuation的意義在于:高性能的程序通常都是跑在IO邊界或者UI事件的邊界上的,TPL的continuation可以更方便的寫這種高scalability的代碼。Task會(huì)根據(jù)一些flag,比如是不是long-running來(lái)決定底層用Thread還是ThreadPool 結(jié)論:能用Task就用Task,底下都是用的Thread或者ThreadPool。但是要注意細(xì)節(jié),比如告訴Task是不是long-running;比如盡量別Wait;再比如IO之后的continuation要盡快結(jié)束然后把線程還回去,有事開個(gè)Worker做,要不然會(huì)影響后面的IO,等等。 |
|