本筆記摘抄自: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(); }
運行結果如下: