我們已經看過一些線程的例子了。盡管我們將要在下一章深入介紹同步問題,但就目前來說還沒有介紹過它。由於線程與應用程序代碼中的其他代碼相比是無序運行的,我們不能確定在一個線程中影響一個特定共享資源的動作會在另外一個線程訪問同樣共享資源之前完成。有很多方法處理這些問題,但是這里我們將介紹一種簡單方式;使用定時器/時鍾。通過定時器,我們可以確定一個方法在一個特定時間間隔內執行,這個方法可以在繼續運行之前檢查需要的動作是否已經完成。這是一個非常簡單的模型,但是可以應用到很多場景中去。
時鍾由兩個對象組成,一個TimerCallback 和 一個定時器。TimerCallback委托定義了在一個特定間隔內要調用的方法,而定時器就是時鍾本身。TimerCallback與定時器的一個特定方法關聯。定時器的構造函數(被重載)有四個參數。第一個參數是之前定義的TimerCallback。第二個一個是用來向特定方法傳輸狀態值的對象(相當於一個是函數名,一個是函數參數)。最后兩個參數是開始定時方法調用前的等待時間和順序TimerCallback方法調用的間隔。這兩個參數可以輸入整數/長整型表示的毫秒數,但是你可以從下面的例子中看到,另外一個選擇是使用System.TimeSpan對象,這樣的話無論你使用ticks, 毫秒,秒,分鍾,小時或者天都是可以的。
想要了解上面描述的邏輯是如何運行的最簡單方式就是看一段代碼,下面的這段代碼啟動兩個線程。第二個線程會在第一個完成工作以后再開始運行;thread_timer.cs:
/************************************* /* Copyright (c) 2012 Daniel Dong * * Author:oDaniel Dong * Blog:o www.cnblogs.com/danielWise * Email:o guofoo@163.com * */ using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace SimpleThread { public class ThreadTimer { private string message; private static Timer tmr; private static bool complete; static void Main() { ThreadTimer obj = new ThreadTimer(); Thread t = new Thread(new ThreadStart(obj.GenerateText)); t.Start(); TimerCallback tmrCallBack = new TimerCallback(obj.GetText); tmr = new Timer(tmrCallBack, null, TimeSpan.Zero, TimeSpan.FromSeconds(2)); //Start immediately, loop for every 2 seconds. //Dispose corresponding resources immediately after do not use the timer. do { if (complete) { break; } } while (true); Console.WriteLine("Exit Main()."); Console.ReadLine(); } public void GenerateText() { StringBuilder sb = new StringBuilder(); for (int i = 1; i < 20; i++) { sb.Append("This is line "); sb.Append(i.ToString()); sb.Append(Environment.NewLine); } message = sb.ToString(); } public void GetText(object state) { if (string.IsNullOrEmpty(message)) { return; } Console.WriteLine("Message is: "); Console.WriteLine(message); tmr.Dispose(); complete = true; } } }
輸出如下結果:
線程生成線程
我們已經通過代碼知道如何從void Main()生成一個線程。在一個類似的方式下,我們也可以在一個線程內生成多個線程。例如,假設有一個Car類,它有一個公共方法StartTheEngine().StartTheEngine()方法調用另外三個私有方法:CheckTheBattery(), CheckForFuel()以及CheckTheEngine().由於檢查電池,油料和引擎這些任務可以同時發生,我們可以在不同線程中運行這些方法。這里是Car類如何在thread_spinning.cs中實現的:
/************************************* /* Copyright (c) 2012 Daniel Dong * * Author:oDaniel Dong * Blog:o www.cnblogs.com/danielWise * Email:o guofoo@163.com * */ using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace SimpleThread { class Car { public void StartTheEngine() { Console.WriteLine("Starting the engine!"); //Declare three new threads. Thread batt = new Thread(new ThreadStart(CheckTheBattery)); Thread fuel = new Thread(new ThreadStart(CheckForFuel)); Thread eng = new Thread(new ThreadStart(CheckTheEngine)); batt.Start(); fuel.Start(); eng.Start(); for (int i = 1; i < 100000000; i++) { //some real executing code here. } Console.WriteLine("Engine is ready!"); Console.ReadLine(); } public void CheckTheBattery() { Console.WriteLine("Checking the Battery!"); for (int i = 1; i < 100000000; i++) { //some real executing code here. } Console.WriteLine("Finished checking the Battery!"); } public void CheckForFuel() { Console.WriteLine("Checking for Fuel!"); for (int i = 1; i < 100000000; i++) { //some real executing code here. } Console.WriteLine("Fuel is available!"); } public void CheckTheEngine() { Console.WriteLine("Checking the Engine!"); for (int i = 1; i < 100000000; i++) { //some real executing code here. } Console.WriteLine("Finished checking the engine!"); } } }
在StartTheEngine()方法內,我們創建了三個線程並依次啟動他們。讓我們在我們的類中加一個入口以便於我們可以看到代碼執行結果:
static void Main(string[] args) { Console.WriteLine("Entering void Main!"); int j; Car myCar = new Car(); Thread worker = new Thread(new ThreadStart(myCar.StartTheEngine)); worker.Start(); for (int i = 1; i < 100000000; i++) { // } Console.WriteLine("Exiting void Main!"); Console.ReadLine(); }
在Void Main()方法中我們又創建了一個新線程並在那個線程中執行StartTheEngine()方法,如圖片1所示:
圖1
輸出結果如下:
如上圖所示,這些方法每個都在自己的線程中按照對應時間片執行。
線程生成線程
我們可以將Car類拆分成單獨的類同時可以在一個新的Engine類中創建兩個新方法:Check1() 和 Check2(). 然后Engine類將會如圖2顯示的那樣在自己的線程中執行Check1() 和 Check2()方法。
圖2
我們將從Car類中移出CheckTheEngine()方法,然后創建一個新的Engine類;thread_spinning2.cs:
/************************************* /* Copyright (c) 2012 Daniel Dong * * Author:oDaniel Dong * Blog:o www.cnblogs.com/danielWise * Email:o guofoo@163.com * */ using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace SimpleThread { class Engine { public void CheckTheEngine() { Thread chck1 = new Thread(new ThreadStart(Check1)); Thread chck2 = new Thread(new ThreadStart(Check2)); chck1.Start(); chck2.Start(); Console.WriteLine("Checking the engine!"); for (int i = 1; i < 100000000; i++) { //some real executing code here } Console.WriteLine("Finished checking the engine!"); } private void Check1() { Console.WriteLine("Starting the engine check!"); for (int i = 1; i < 100000000; i++) { //some real executing code here } Console.WriteLine("Finished engine check 1!"); } private void Check2() { Console.WriteLine("Starting the engine check2!"); for (int i = 1; i < 100000000; i++) { //some real executing code here } Console.WriteLine("Finished engine check 2!"); } } }
Engine類的公共方法CheckTheEngine()創建了另外兩個線程並調用Check1 和 Check2 方法。下面的輸出結果:
通過上面的介紹我們知道從線程中創建線程非常簡單。然而,你可能對這種方式下的劣勢感興趣:活躍線程數量上升以后,性能就會下降。
性能考慮
你創建越多的線程,系統就得維護更多的線程上下文和CPU指令。Windows任務管理器的進程選項卡將告訴你有多少個進程和線程正在運行。然而,從任務管理器里看到的是操作系統進程 ,它們與AppDomains是不同的。你可以在使用線程窗口調試一個指定的.NET 應用程序時查看運行的線程。
如果你想看在CLR中有多少個線程在運行,你可以使用Windows性能監視器工具並添加一系列特定的CLR性能類別。CLR對外提供一個.NET CLR LocksAndThreads的性能計數器類別,我們可以使用這個類別來獲取更多關於CLR托管線程的信息。現在運行性能監視器並從.NET CLR LocksAndThread類別下添加如下表所示的計數器。
這里是thread_spinning2.cs 應用顯示情況:
這里是對“.NET CLR LocksAndThreads”性能計數器信息的大體描述:
1. # of current logical Threads 計數器確定有304個托管線程由CLR 創建並擁有,由於我們添加了“_Global_”計數器實例,所以我們可以看到由CLR創建的所有線程。
2. # of current physical Threads 計數器確定有288個操作系統線程由CLR創建並擁有。
3. # of total recognized Threads計數器確定由19個操作系統線程由線程對象創建且被CLR識別出來。
4. Total # of Contentions 計數器確定了當運行時試圖獲取托管鎖時發生了121次錯誤。對執行代碼來說發生托管鎖失敗是很糟糕的。
下一篇我們將介紹線程生命周期…