本筆記摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/18/Thread.html,記錄一下學習過程以備後續查用。 一、線程的介紹 進程(Process)是應用程式的實例要使用的資源的一個集合,每個應用程式都在各自的進程中運行來確保應用程式不受其他 ...
本筆記摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/18/Thread.html,記錄一下學習過程以備後續查用。
一、線程的介紹
進程(Process)是應用程式的實例要使用的資源的一個集合,每個應用程式都在各自的進程中運行來確保應用程式不受其他應用程式的影響。
線程是進程中基本執行單元, 一個進程中可以包含多個線程。在進程入口執行的第一個線程是一個進程的主線程,在.NET應用程式中,都是以Main()方法
作為程式的入口(線程是進程的執行單元,進程是線程的一個容器)。
二、線程調度和優先順序
Windows之所以被稱為搶占式多線程操作系統,是因為線程可以在任意時間被搶占,並調度另一個線程。
每個線程都分配了從0~31的一個優先順序,系統首先把高優先順序的線程分配給CPU執行。
Windows 支持7個相對線程優先順序:Idle、Lowest、Below Normal、Normal、Above Normal、Highest和Time-Critical。Normal是預設的線程優先順序,
然而在程式中可以通過設置Thread的Priority屬性來改變線程的優先順序,它的類型為ThreadPriority枚舉類型:Lowest、BelowNormal、Normal、AboveNormal
和Highest,CLR為自己保留了 Idle和Time-Critical優先順序。
枚舉值列表如下:
成員名稱 | 說明 |
Lowest | 可以將Thread置於其他優先順序線程之後。 |
BelowNormal | 可以將Thread置於Normal優先順序線程之後Lowest優先順序線程之前。 |
Normal |
可以將Thread置於AboveNormal優先順序線程之後BelowNormal優先順序線程之前。 預設情況下,線程置於Normal優先順序。 |
AboveNormal | 可以將Thread置於Highest優先順序線程之後Normal優先順序線程之前。 |
Highest | 可以將Thread置於其他優先順序線程之前。 |
三、前臺線程和後臺線程
在.NET中線程分為前臺線程和後臺線程:
1、主線程是程式開始時就執行的,如果你需要再創建線程,那麼創建的線程就是這個主線程的子線程,它是前臺線程。
2、子線程可以是前臺線程也可以是後臺線程。
3、前臺線程必須全部執行完,即使主線程關閉掉,這時進程仍然存活。
4、當所有前臺線程停止運行時,CLR會強制結束仍在運行的任何後臺線程,這些後臺線程直接被終止,不會拋出異常。
5、前臺線程與後臺線程唯一的區別是後臺線程不會阻止進程終止,可以在任何時候將前臺線程修改為後臺線程。
static void Main(string[] args) { ThreadType(); } /// <summary> /// 前臺線程與後臺線程 /// </summary> private static void ThreadType() { //創建一個新線程(預設為前臺線程) Thread backThread = new Thread(Worker) { //將線程更改為後臺線程 IsBackground = true }; //通過Start方法啟動線程 backThread.Start(); //如果backThread是前臺線程,則應用程式5秒後才終止。 //如果backThread是後臺線程,則應用程式立即終止。 Console.WriteLine("It's from main thread."); //Console.Read(); } private static void Worker() { //休息5秒 Thread.Sleep(5000); Console.WriteLine("It's from worker thread."); }
假如保留IsBackground = true;但又想繼續執行Worker()方法的話,可以調用Thread.Join()方法來實現。Join()方法能保證主線程(前臺線程)在非同步線程
Thread(後臺線程)運行結束後才會運行。
註1:一個線程在執行的過程中,可能調用另一個線程,前者可以稱為調用線程,後者成為被調用線程。
註2:Thread.Join()方法的使用場景:調用線程掛起,等待被調用線程執行完畢後,繼續執行。
註3:被調用線程執行Join方法,告訴調用線程,你先暫停,我執行完了,你再執行。從而保證了先後關係。
static void Main(string[] args) { ThreadStatusChange(); } /// <summary> /// 線程狀態之間的轉換 /// </summary> private static void ThreadStatusChange() { //創建一個新線程(預設為前臺線程) Thread backThread = new Thread(Worker) { //將線程更改為後臺線程 IsBackground = true }; //通過Start方法啟動線程 backThread.Start(); //Join()方法能保證主線程(前臺線程)在非同步線程Thread(後臺線程)運行結束後才會運行 backThread.Join(); Console.WriteLine("It's from main thread."); Console.Read(); } private static void Worker() { //休息5秒 Thread.Sleep(5000); Console.WriteLine("It's from worker thread."); }
運行結果如下:
四、 Suspend和Resume方法
這兩個方法在.NET Framework 1.0的時候就支持的方法,他們分別可以掛起線程及恢復掛起的線程,但在.NET Framework 2.0以後的版本中這兩個方法都過時了。
MSDN的解釋是這樣:
警告:
不要使用Suspend和Resume方法來同步線程的活動。您無法知道掛起線程時它正在執行什麼代碼。如果您在安全許可權評估期間掛起持有鎖的線程,
則AppDomain中的其他線程可能被阻止。如果您線上程正在執行類構造函數時掛起它,則 AppDomain中嘗試使用該類的其他線程將被阻止。這樣很容易發生死鎖。
static void Main(string[] args) { ThreadResume(); } /// <summary> /// 線程恢復 /// </summary> private static void ThreadResume() { Thread thread = new Thread(ThreadSuspend) { Name = "Thread1" }; thread.Start(); Thread.Sleep(2000); Console.WriteLine("Main Thread is running."); //線程恢復 thread.Resume(); Console.Read(); } /// <summary> /// 線程掛起 /// </summary> private static void ThreadSuspend() { Console.WriteLine("Thread: {0} has been suspended.", Thread.CurrentThread.Name); //將當前線程掛起 Thread.CurrentThread.Suspend(); Console.WriteLine("Thread: {0} has been resumed.", Thread.CurrentThread.Name); }
在上面這段代碼中Thread1線程是在主線程中恢復的,但當主線程發生異常時,這時候Thread1就會一直處於掛起狀態,此時Thread1所使用的資源就不能釋放
(除非強制終止進程),當其它的線程需要使用這快資源的時候, 很有可能就會發生死鎖現象。
上面一段代碼還存在一個隱患,假如把Thread.Sleep(2000);這段代碼註釋一下:
static void Main(string[] args) { ThreadResume(); } /// <summary> /// 線程恢復 /// </summary> private static void ThreadResume() { Thread thread = new Thread(ThreadSuspend) { Name = "Thread1" }; thread.Start(); //Thread.Sleep(2000); Console.WriteLine("Main Thread is running."); //線程恢復 thread.Resume(); Console.Read(); } /// <summary> /// 線程掛起 /// </summary> private static void ThreadSuspend() { Console.WriteLine("Thread: {0} has been suspended.", Thread.CurrentThread.Name); //將當前線程掛起 Thread.CurrentThread.Suspend(); Console.WriteLine("Thread: {0} has been resumed.", Thread.CurrentThread.Name); }
這個時候,主線程因為跑(運行)得太快,做完自己的事情去喚醒Thread1時,此時Thread1還沒有掛起,而此時喚醒Thread1就會出現異常了。
五、Abort和Interrupt方法
Abort方法和Interrupt都是用來終止線程的,但是兩者還是有區別的:
1、它們拋出的異常不一樣:Abort 方法拋出的異常是ThreadAbortException,Interrupt拋出的異常為ThreadInterruptedException。
2、調用Interrupt方法的線程之後可以被喚醒,然而調用Abort方法的線程就直接被終止不能被喚醒了。
下麵演示Abort方法的使用:
static void Main(string[] args) { //ThreadType(); //ThreadStatusChange(); //ThreadResume(); ThreadAbort(); } /// <summary> /// 線程中斷(不可再喚醒) /// </summary> private static void ThreadAbort() { Thread threadAbort = new Thread(AbortMethod) { Name = "ThreadAbort" }; threadAbort.Start(); Thread.Sleep(1000); try { threadAbort.Abort(); } catch { Console.WriteLine("1-> {0} exception happen in main thread.", Thread.CurrentThread.Name); Console.WriteLine("2-> {0} status is:{1} in main thread.", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState); } finally { Console.WriteLine("3-> {0} status is:{1} in main thread.", threadAbort.Name, threadAbort.ThreadState); } threadAbort.Join(); Console.WriteLine("4-> {0} status is:{1}", threadAbort.Name, threadAbort.ThreadState); Console.Read(); } /// <summary> /// Abort方法 /// </summary> private static void AbortMethod() { try { Thread.Sleep(5000); } catch (Exception e) { Console.WriteLine(e.GetType().Name); Console.WriteLine("5-> {0} exception happen in abort thread.", Thread.CurrentThread.Name); Console.WriteLine("6-> {0} status is:{1} in abort thread.", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState); } finally { Console.WriteLine("7-> {0} status is:{1} in abort thread.", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState); } }
運行結果如下:
從運行結果可以看出,調用Abort方法的線程引發的異常類型為ThreadAbortException,另外異常只會在調用Abort方法的線程中發生,而不會在主線程中拋出,
其次調用Abort方法後線程的狀態不是立即改變為Aborted狀態,而是從AbortRequested->Aborted。
下麵演示Interrupt方法的使用:
static void Main(string[] args) { ThreadInterrupt(); } /// <summary> /// 線程中斷(可再喚醒) /// </summary> static void ThreadInterrupt() { Thread threadInterrupt = new Thread(InterruptMethod) { Name = "ThreadInterrupt" }; threadInterrupt.Start(); threadInterrupt.Interrupt(); threadInterrupt.Join(); Console.WriteLine("1-> {0} status is:{1} ", threadInterrupt.Name, threadInterrupt.ThreadState); Console.Read(); } /// <summary> /// Interrupt方法 /// </summary> private static void InterruptMethod() { try { Thread.Sleep(5000); } catch (Exception e) { Console.WriteLine(e.GetType().Name); Console.WriteLine("2-> {0} exception happen in interrupt thread.", Thread.CurrentThread.Name); Console.WriteLine("3-> {0} status is:{1} in interrupt thread.", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState); } finally { Console.WriteLine("4-> {0} status is:{1} in interrupt thread.", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState); } }
運行結果如下:
從結果中可以得到,調用Interrupt方法拋出的異常為:ThreadInterruptException, 另外當調用Interrupt方法後線程的狀態應該是中斷的,但是從運行結果看,
此時的線程因為Join、Sleep方法而喚醒了線程。
為了進一步解釋調用Interrupt方法的線程可以被喚醒, 我們可以線上程執行的方法中運用迴圈,如果線程可以喚醒,則輸出結果中就一定會有迴圈的部分,
而調用Abort方法的線程則不會有迴圈的部分。
下麵代碼相信大家看後肯定會更加理解兩個方法的區別:
static void Main(string[] args) { //ThreadType(); //ThreadStatusChange(); //ThreadResume(); //ThreadAbort(); //ThreadInterrupt(); ThreadWake(); } /// <summary> /// 線程喚醒 /// </summary> static void ThreadWake() { Thread threadWake = new Thread(WakeMethod); threadWake.Start(); Thread.Sleep(100); threadWake.Interrupt(); Thread.Sleep(3000); Console.WriteLine("1-> After finally block,the threadWake status is:{0}", threadWake.ThreadState); Console.Read(); } /// <summary> /// Wake方法 /// </summary> private static void WakeMethod() { for (int i = 0; i < 4; i++) { try { Thread.Sleep(2000); Console.WriteLine("2-> Thread is Running."); } catch (Exception ex) { if (ex != null) { Console.WriteLine("3-> Exception {0} throw.", ex.GetType().Name); } } finally { Console.WriteLine("4-> Current thread status is:{0}", Thread.CurrentThread.ThreadState); } } }
運行結果如下:
如果把上面的threadWake.Interrupt();改為threadWake.Abort(); 運行結果為:
六、簡單線程的使用
其實在上面介紹前臺線程和後臺線程的時候已經通過ThreadStart委托創建一個線程了,此時已經實現了一個多線程的一個過程。
下麵通過ParameterizedThreadStart委托的方式來實現多線程:
static void Main(string[] args) { ThreadTypeUseParameterized(); } /// <summary> /// 前臺線程與後臺線程(使用ParameterizedThreadStart委托的方式來實現多線程) /// </summary> private static void ThreadTypeUseParameterized() { //創建一個新線程(預設為前臺線程) Thread backThread = new Thread(new ParameterizedThreadStart(Worker1)); //通過Start方法啟動線程 backThread.Start(123); //如果backThread是前臺線程,則應用程式5秒後才終止。 //如果backThread是後臺線程,則應用程式立即終止。 Console.WriteLine("It's from main thread."); } private static void Worker1(object parameter) { //休息5秒 Thread.Sleep(5000); Console.WriteLine(parameter+" is from worker1 thread."); Console.Read(); }
運行結果如下: