簡單理解
Thread:是一個指令序列,個體對象。
Threadpool:在使用Thread的過程中,程序員要為每個希望並發的序列new一個線程,很麻煩,因此希望有一個統一管理線程的方法,程序員就不需要關注線程的申請管理問題,所以就對Thread進行一系列封裝,有了ThreadPool。使用Threadpool,把需要並發的序列添加進線程池,線程池根據其線程列表中的線程的空閒情況,動態為並發序列申請線程。
Task:再後來,程序員發現在使用Threadpool的過程當中還是存在很多不便,比如:(1)ThreadPool不支持線程的取消、完成、失敗通知等交互性操作;(2)ThreadPool不支持線程執行的先後次序;那麼怎麼辦,那就繼續對ThreadPool進行封裝呗,於是.net Framework4.0有了TPL和Task。
Thread
選用Thread的一個重要原因之一是因為它的Thread.Sleep()成員,因為它在Task或ThreadPool中都沒有等價物。不過如果不會帶來太多沒必要的復雜性,可以考慮用一個計時器代替Sleep()。
對於Thread這個知識點,重點在於同步、死鎖和競態等問題,感覺關鍵點在於對lock()和WaitHandler的理解。這裡,介紹下參數的傳遞和獲取。
下面這部分主要查看MSDN的文檔:http://msdn.microsoft.com/en-us/library/wkays279.aspx
因為Thread的構造函數需要傳遞一個無返回類型和無參的委托引用,因此最理想的辦法是把需要傳遞給線程的方法封裝在一個類裡面,然後將需要傳遞給這個線程的參數定義為該類的字段。比如,要把下面這個帶參函數通過線程調用:
1 public double CalArea(double length,double width) 2 { 3 double Area=length*width; 4 return Area; 5 }
可使用如下方法進行:
1 public class AreaClass 2 { 3 public double width; 4 public double length; 5 public double area; 6 public void CalArea() 7 { 8 area=width*length; 9 } 10 } 11 12 public void TestAreaCal() 13 { 14 AreaClass areaCal=new AreaClass(); 15 System.Threading.Thread thread1=new System.Threading.Thread(areaCal.CalArea); 16 areaCal.width=2; 17 areaCal.length=3; 18 thread1.start(); 19 }
但要注意,如果在執行完thread1.start()之後,立刻查看areaCal.area的值,並不一定就存在了。要獲得有效的返回值得簡單方法將在下面介紹。
簡單方法是使用BackgroundWorker類。利用BackgroundWorker類管理你的線程,並在線程執行完畢後產生一個事件,然後在對應的事件處理函數中處理結果。
1 class AreaClass2 2 { 3 public double Base; 4 public double Height; 5 public double CalcArea() 6 { 7 // Calculate the area of a triangle. 8 return 0.5 * Base * Height; 9 } 10 } 11 12 private System.ComponentModel.BackgroundWorker BackgroundWorker1 13 = new System.ComponentModel.BackgroundWorker(); 14 15 private void TestArea2() 16 { 17 InitializeBackgroundWorker(); 18 19 AreaClass2 AreaObject2 = new AreaClass2(); 20 AreaObject2.Base = 30; 21 AreaObject2.Height = 40; 22 23 // Start the asynchronous operation. 24 BackgroundWorker1.RunWorkerAsync(AreaObject2); 25 } 26 27 private void InitializeBackgroundWorker() 28 { 29 // Attach event handlers to the BackgroundWorker object. 30 BackgroundWorker1.DoWork += 31 new System.ComponentModel.DoWorkEventHandler(BackgroundWorker1_DoWork); 32 BackgroundWorker1.RunWorkerCompleted += 33 new System.ComponentModel.RunWorkerCompletedEventHandler(BackgroundWorker1_RunWorkerCompleted); 34 } 35 36 private void BackgroundWorker1_DoWork( 37 object sender, 38 System.ComponentModel.DoWorkEventArgs e) 39 { 40 AreaClass2 AreaObject2 = (AreaClass2)e.Argument; 41 // Return the value through the Result property. 42 e.Result = AreaObject2.CalcArea(); 43 } 44 45 private void BackgroundWorker1_RunWorkerCompleted( 46 object sender, 47 System.ComponentModel.RunWorkerCompletedEventArgs e) 48 { 49 // Access the result through the Result property. 50 double Area = (double)e.Result; 51 MessageBox.Show("The area is: " + Area.ToString()); 52 }
ThreadPool
ThreadPool是一個靜態類。
管理過程:
許多程序生成的線程有一大半時間處在休眠狀態,直到產生一個事件喚醒它們,另一部分線程只會被周期性地喚醒以更改狀態信息。ThreadPool可以通過為你的應用程序提供由系統管理的線程來提高應用程序對線程的使用率。線程池中會有一個線程監看線程池隊列中的等待操作,當線程池中的線程完成它的任務後,它就會回到等待線程的隊列當中,等待重新使用。如果線程池中的所有線程都在運行,那麼新的任務會加進等待隊列中。對線程的重復使用降低了為每一個任務都開啟一個新線程的造成的程序開銷。
特點:
ThreadPool裡面的線程管理是由類庫(或操作系統)完成的,而不是由應用程序進行管理。
線程池裡面的線程都是後台線程(即它們的isBackgroud屬性為true)。也就意味著當所有前台線程都運行完畢後,線程池的線程不會保持程序的運行。
線程池一般使用在服務應用程序上。每個進來的請求都分配給線程池中的每一個線程,這樣就可以異步處理各個請求。
使用:
線程池的簡單使用可通過QueueUserWorkItem方法進行。如果需要向任務中傳遞參數,可以通過傳遞一個state object來進行,如果有多個參數要傳遞,可將多個參數封裝在結構或對象的字段當中。但要注意,一旦一個方法加入了線程隊列,就無法取消該方法的執行:
public static void Main() { System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(ASynTask),ASynData); //other code snippets...... } public void ASynTask(object state) { //codes need to executed asynchronously }
線程池的大小可以通過GetMaxThreads或SetMaxThreads方法訪問或設置,但注意過大或過小都會影響程序的性能。
另外,獲取ThreadPool返回值有兩種方法,具體查看上面Thread所講的辦法。
Task和TPL
關於Task和TPL的知識還是以後再詳細介紹。這裡簡單講下Task的主要特征。
1 public static void Main() 2 { 3 CancellationTokenSource cancellationTokenSource=new CancellationTokenSource(); 4 Task task.Task.Factory.StartNew(()=>LoopWork(cancellationTokenSource.Token),cancellationTokenSource.Token); 5 Thread.sleep(1000); 6 cancellationTokenSource.Cancel(); 7 task.wait(); 8 } 9 10 private static void LoopWork(CancellationToken cancellationToken) 11 { 12 while(!cancellationToken.IsCancellationRequested) 13 { 14 //work need to be done 15 } 16 }
Task還支持IDisposable方法,這是支持Wait()功能所需要的。
並行迭代
在使用for循環迭代時,處理器會按順序依次迭代。但如果每次迭代之間互不影響,而且如果系統有多個處理器,我們可以讓每個處理器都負責一個迭代,從而提高效率。
相關類:System.Threading.Tasks.Parallel。
相關方法:Parallel.For()和Parallel.Foreach()
注意三點:計算機具有多個處理器,並行迭代的順序不是固定的,在使用時還要同時考慮迭代內部代碼的非原子性帶來的競態問題。
1 //將10000個小寫單詞變成大寫 2 static void Main() 3 { 4 int iterations=10000; 5 string[] word=new string[iterations]; 6 //此處初始化word,輸入10000個小寫單詞 7 Parallel.For(0,iterations,(i)=> 8 { 9 word[i]=word[i].toUpper(); 10 }); 11 }