在上一篇多線程(基礎篇1)中,我們主要講述了如何創建線程、中止線程、線程等待以及終止線程的相關知識,在本篇中我們繼續講述有關線程的一些知識。
五、確定線程的狀態
在這一節中,我們將講述如何查看一個線程的狀態,通常知道一個線程處於什麼狀態是非常有用的。但是,要注意線程是獨立運行的,它的狀態可能隨時變化。要查看一個線程的狀態,我們可以按如下步驟進行:
1、使用Visual Studio 2015創建一個新的控制台應用程序。
2、雙擊打開“Program.cs”文件,然後修改為如下代碼:
1 using System; 2 using System.Threading; 3 using static System.Console; 4 using static System.Threading.Thread; 5 6 namespace Recipe05 7 { 8 class Program 9 { 10 static void DoNothing() 11 { 12 Sleep(TimeSpan.FromSeconds(2)); 13 } 14 15 static void PrintNumbersWithStatus() 16 { 17 WriteLine("Starting..."); 18 WriteLine(CurrentThread.ThreadState.ToString()); 19 20 for(int i = 1; i < 10; i++) 21 { 22 Sleep(TimeSpan.FromSeconds(2)); 23 WriteLine(i); 24 } 25 } 26 27 static void Main(string[] args) 28 { 29 WriteLine("Starting program..."); 30 31 Thread t1 = new Thread(PrintNumbersWithStatus); 32 Thread t2 = new Thread(DoNothing); 33 34 WriteLine(t1.ThreadState.ToString()); 35 36 t2.Start(); 37 t1.Start(); 38 39 for(int i = 1; i < 30; i++) 40 { 41 WriteLine(i.ToString() + " - " + t1.ThreadState.ToString()); 42 } 43 44 Sleep(TimeSpan.FromSeconds(6)); 45 46 t1.Abort(); 47 48 WriteLine("A thread has been aborted"); 49 WriteLine(t1.ThreadState.ToString()); 50 WriteLine(t2.ThreadState.ToString()); 51 } 52 } 53 }
3、運行該控制台應用程序,運行效果(每次運行效果可能不同)如下圖所示:
在上述代碼中,我們在“Main”方法中定義了兩個不同的線程t1和t2,t1線程在執行過程中被終止,t2線程成功執行完畢。我們可以使用Thread對象的ThreadState屬性獲得某個線程的狀態信息,ThreadState屬性是C#枚舉類型。
當程序執行到第34行代碼處時,t1線程還沒有執行,這個時候t1線程的狀態為“ThreadState.Unstarted”。
當程序執行到第37行代碼處時,t1線程開始執行,這個時候該線程的狀態為“ThreadState.Running”。
當運行到第39~42行代碼處時,主線程開始執行循環語句,持續打印出29條t1線程的狀態,因為在“PrintNumbersWithStatus”方法中調用了“Thread.Sleep”方法,因此在執行主線程的循環的過程中,t1線程的狀態在“ThreadState.Running”和“ThreadState.WaitSleepJoin”之間轉換。
當程序執行到第44行代碼處時,在主線程中調用了“Thread.Sleep”方法,在等待6秒期間,“PrintNumbersWithStatus”方法中的for循環代碼不斷執行,因此打印出1、2、3共三行數字。
當程序執行到第46行代碼處時,我們調用了t1的“Abort”方法,從而t1線程被終止。
當程序執行到第49行代碼處時,由於已經在t1線程上調用了“Abort”方法,因此,它的狀態為“ThreadState.Aborted”或“ThreadState.AbortRequested”。
當程序執行到第50行代碼處時,因為t2線程正常執行完畢,因此t2線程的狀態為“ThreadState.Stopped”。
六、線程優先級
在這一小節中,我們將講述線程的優先級,線程的優先級確定了線程所能使用CPU時間的大小。我們按以下步驟來完成線程優先級的學習:
1、使用Visual Studio 2015創建一個新的控制台應用程序。
2、雙擊打開“Program.cs”文件,修改代碼如下所示:
1 using System; 2 using System.Threading; 3 using static System.Console; 4 using static System.Threading.Thread; 5 using static System.Diagnostics.Process; 6 7 namespace Recipe06 8 { 9 class ThreadSample 10 { 11 private bool isStopped = false; 12 13 public void Stop() 14 { 15 isStopped = true; 16 } 17 18 public void CountNumbers() 19 { 20 long counter = 0; 21 while (!isStopped) 22 { 23 counter++; 24 } 25 26 WriteLine($"{CurrentThread.Name} with {CurrentThread.Priority,10} priority has a count = {counter,15:N0}"); 27 } 28 } 29 30 class Program 31 { 32 static void RunThreads() 33 { 34 var sample = new ThreadSample(); 35 36 var threadOne = new Thread(sample.CountNumbers); 37 threadOne.Name = "ThreadOne"; 38 39 var threadTwo = new Thread(sample.CountNumbers); 40 threadTwo.Name = "ThreadTwo"; 41 42 threadOne.Priority = ThreadPriority.Highest; 43 threadTwo.Priority = ThreadPriority.Lowest; 44 45 threadOne.Start(); 46 threadTwo.Start(); 47 48 Sleep(TimeSpan.FromSeconds(2)); 49 sample.Stop(); 50 } 51 52 static void Main(string[] args) 53 { 54 WriteLine($"Current thread priority: {CurrentThread.Priority}"); 55 WriteLine("Running on all cores available"); 56 57 RunThreads(); 58 59 Sleep(TimeSpan.FromSeconds(2)); 60 61 WriteLine("Running on a single core"); 62 GetCurrentProcess().ProcessorAffinity = new IntPtr(1); 63 RunThreads(); 64 } 65 } 66 }
3、運行該控制台應用程序,運行效果(每次運行效果可能不同)如下圖所示:
在上述代碼中,我們定義了兩個不同的線程threadOne和threadTwo。threadOne線程具有最高的優先級“ThreadPriority.Highest”,threadTwo具有最低的優先級“ThreadPriority.Lowest”。
在程序執行到第57行代碼處,如果我們的計算機是多核計算機,我們將在2秒內獲得初始結果,並且具有最高優先級的threadOne線程要比具有最低優先級的threadTwo線程的迭代次數更多一些,但也不會相差太遠。但是,如果我們的計算機是單核計算機,則結果大不相同。
為了模擬單核計算機,我們將ProcessorAffinity的值設置為1,現在這兩個線程所迭代的次數將差異非常大,並且程序的執行時間遠遠大於2秒。這是因為CPU的計算機時間大部分給予了高優先級的線程,而低優先級的線程獲得非常少的CPU時間。
七、前台線程和後台線程
在這一小節中,我們將講述什麼是前台線程和後台線程,並且講述如何設置這個選項以影響程序的行為。我們按以下步驟來完成前台線程和後台線程的學習:
1、使用Visual Studio 2015創建一個新的控制台應用程序。
2、雙擊打開“Program.cs”文件,修改代碼如下所示:
1 using System; 2 using System.Threading; 3 using static System.Console; 4 using static System.Threading.Thread; 5 6 namespace Recipe07 7 { 8 class ThreadSample 9 { 10 private readonly int iterations; 11 12 public ThreadSample(int iterations) 13 { 14 this.iterations = iterations; 15 } 16 17 public void CountNumbers() 18 { 19 for(int i = 0; i < iterations; i++) 20 { 21 Sleep(TimeSpan.FromSeconds(0.5)); 22 WriteLine($"{CurrentThread.Name} prints {i}"); 23 } 24 } 25 } 26 27 class Program 28 { 29 static void Main(string[] args) 30 { 31 var sampleForeground = new ThreadSample(10); 32 var sampleBackground = new ThreadSample(20); 33 34 var threadOne = new Thread(sampleForeground.CountNumbers); 35 threadOne.Name = "ForegroundThread"; 36 37 var threadTwo = new Thread(sampleBackground.CountNumbers); 38 threadTwo.Name = "BackgroundThread"; 39 threadTwo.IsBackground = true; 40 41 threadOne.Start(); 42 threadTwo.Start(); 43 } 44 } 45 }
3、運行該控制台應用程序,運行效果(每次運行效果可能不同)如下圖所示:
在上述代碼中,我們定義了兩個不同的線程threadOne和threadTwo。當我們創建一個線程時,該線程顯示地是一個前台線程,比如threadOne。如果我們要將一個線程設置為後台線程,只需要將該線程的“IsBackground”屬性設置為“true”即可,比如threadTwo。我們還配置了兩個線程執行的循環次數不一樣,threadOne線程所執行的循環次數為10次,threadTwo線程所執行的循環次數為20次,因此threadOne線程要比threadTwo線程早執行完畢。
從執行結果上來看,當threadOne線程執行完畢後,主程序也結束了,並且後台線程也被終止了,這就是前台線程和後台線程的區別:進程會等待所有的前台進程執行完畢後才結束,單不會等待後台進程執行完畢。
八、向線程傳遞參數
在這一小節,我將講述如何向一個線程傳遞參數,我們將使用不同的方法來完成這個工作,具體步驟如下所示:
1、使用Visual Studio 2015創建一個新的控制台應用程序。
2、雙擊打開“Program.cs”文件,修改代碼如下所示:
1 using System; 2 using System.Threading; 3 using static System.Console; 4 using static System.Threading.Thread; 5 6 namespace Recipe08 7 { 8 class ThreadSample 9 { 10 private readonly int iterations; 11 12 public ThreadSample(int iterations) 13 { 14 this.iterations = iterations; 15 } 16 17 public void CountNumbers() 18 { 19 for(int i = 1; i <= iterations; i++) 20 { 21 Sleep(TimeSpan.FromSeconds(0.5)); 22 WriteLine($"{CurrentThread.Name} prints {i}"); 23 } 24 } 25 } 26 27 class Program 28 { 29 static void Count(object iterations) 30 { 31 CountNumbers((int)iterations); 32 } 33 34 static void CountNumbers(int iterations) 35 { 36 for(int i = 1; i <= iterations; i++) 37 { 38 Sleep(TimeSpan.FromSeconds(0.5)); 39 WriteLine($"{CurrentThread.Name} prints {i}"); 40 } 41 } 42 43 static void PrintNumber(int number) 44 { 45 WriteLine(number); 46 } 47 48 static void Main(string[] args) 49 { 50 var sample = new ThreadSample(10); 51 52 var threadOne = new Thread(sample.CountNumbers); 53 threadOne.Name = "ThreadOne"; 54 threadOne.Start(); 55 threadOne.Join(); 56 57 WriteLine("--------------------------"); 58 59 var threadTwo = new Thread(Count); 60 threadTwo.Name = "ThreadTwo"; 61 threadTwo.Start(8); 62 threadTwo.Join(); 63 64 WriteLine("--------------------------"); 65 66 var threadThree = new Thread(() => CountNumbers(12)); 67 threadThree.Name = "ThreadThree"; 68 threadThree.Start(); 69 threadThree.Join(); 70 71 WriteLine("--------------------------"); 72 73 int i = 10; 74 var threadFour = new Thread(() => PrintNumber(i)); 75 76 i = 20; 77 var threadFive = new Thread(() => PrintNumber(i)); 78 79 threadFour.Start(); 80 threadFive.Start(); 81 } 82 } 83 }
3、運行該控制台應用程序,運行效果如下圖所示:
在第50~55行代碼處,我們首先創建了一個ThreadSample類型的對象,並將數字10傳遞給了該類型的構造方法。然後我們將ThreadSample對象的“CountNumbers”方法傳遞給了線程threadOne的構造方法,“CountNumbers”方法將在線程threadOne中被執行,因此我們通過“CountNumbers”方法將數字10傳遞給了線程threadOne。
在第59~62行代碼處,我們使用“Thread.Start”的重載方法將一個對象傳遞給線程threadTwo,要使該段代碼正確運行,我們要保證在構造threadTwo線程時,傳給給Thread的構造方法的委托要帶有一個object類型的參數。在這段代碼中,我們將8看作一個對象傳遞給“Count”方法,該方法有調用了同名的重載方法將object對象轉換為整數類型。
在第66~69行代碼處,我們在創建threadThree線程時,給Thread構造方法傳遞的是一個lambda表達式,該表達式定義了一個不屬於任何類的方法,我們在該lambda表達式中調用了“CountNumbers”方法,並將12傳遞給了“CountNumbers”方法,通過lambda表達式,我們將12傳遞給了threadThree線程。
注意:當我們將一個局部變量用於多個lambda表達式時,這些lambda表達式將會共享這個變量的值,在第73~80行代碼處,我們將局部變量用於了兩個lambda表達式,我們運行threadFour和threadFive線程後,我們發現打印出來的結果都是20。
未完待續!