引言
本文主要從線程的基礎用法,CLR線程池當中工作者線程與I/O線程的開發,並行操作PLINQ等多個方面介紹多線程的開發。
其中委託的BeginInvoke方法以及回呼函數最為常用。
而 I/O線程可能容易遭到大家的忽略,其實在開發多線程系統,更應該多留意I/O線程的操作。特別是在ASP.NET開發當中,可能更多人只會留意在用戶端使用Ajax或者在伺服器端使用UpdatePanel。其實合理使用I/O線程在通訊項目或檔案下載時,能儘可能地減少IIS的壓力。
並行編程是Framework4.0中極力推廣的非同步作業方式,更值得更深入地學習。
希望本篇文章能對各位的學習研究有所協助,當中有所錯漏的地方敬請點評。
目錄
一、線程的定義
二、線程的基礎知識
三、以ThreadStart方式實現多線程
四、CLR線程池的工作者線程
五、CLR線程池的I/O線程
六、非同步 SqlCommand
七、並行編程與PLINQ
八、計時器與鎖
一、線程的定義
1. 1 進程、應用程式定義域與線程的關係
進程(Process)是Windows系統中的一個基本概念,它包含著一個運行程式所需要的資源。進程之間是相對獨立的,一個進程無法訪問另一個進程的資料(除非利用分散式運算方式),一個進程啟動並執行失敗也不會影響其他進程的運行,Windows系統就是利用進程把工作劃分為多個獨立的地區的。進程可以理解為一個程式的基本邊界。
應用程式定義域(AppDomain)是一個程式啟動並執行邏輯地區,它可以視為一個輕量級的進程,.NET的程式集正是在應用程式定義域中啟動並執行,一個進程可以包含有多個應用程式定義域,一個應用程式定義域也可以包含多個程式集。在一個應用程式定義域中包含了一個或多個上下文context,使用上下文CLR就能夠把某些特殊對象的狀態放置在不同容器當中。
線程(Thread)是進程中的基本執行單元,在進程入口執行的第一個線程被視為這個進程的主線程。在.NET應用程式中,都是以Main()方法作為入口的,當調用此方法時系統就會自動建立一個主線程。線程主要是由CPU寄存器、調用棧和執行緒區域儲存空間(Thread Local Storage,TLS)組成的。CPU寄存器主要記錄當前所執行線程的狀態,調用棧主要用於維護線程所調用到的記憶體與資料,TLS主要用於存放線程的狀態資訊。
進程、應用程式定義域、線程的關係如下圖,一個進程內可以包括多個應用程式定義域,也有包括多個線程,線程也可以穿梭於多個應用程式定義域當中。但在同一個時刻,線程只會處於一個應用程式定義域內。
由於本文是以介紹多線程技術為主題,對進程、應用程式定義域的介紹就到此為止。關於進程、線程、應用程式定義域的技術,在“C#綜合揭秘——細說進程、應用程式定義域與上下文”會有詳細介紹。
1.2 多線程
在單CPU系統的一個單位時間(time slice)內,CPU只能運行單個線程,運行順序取決於線程的優先順序別。如果在單位時間內線程未能完成執行,系統就會把線程的狀態資訊儲存到線程的本機存放區器(TLS) 中,以便下次執行時恢複執行。而多線程只是系統帶來的一個假像,它在多個單位時間內進行多個線程的切換。因為切換頻密而且單位時間非常短暫,所以多線程可被視作同時運行。
適當使用多線程能提高系統的效能,比如:在系統請求大容量的資料時使用多線程,把資料輸出工作交給非同步線程,使主線程保持其穩定性去處理其他問題。但需要注意一點,因為CPU需要花費不少的時間線上程的切換上,所以過多地使用多線程反而會導致效能的下降。
返回目錄
二、線程的基礎知識
2.1 System.Threading.Thread類
System.Threading.Thread是用於控制線程的基礎類,通過Thread可以控制當前應用程式定義域中線程的建立、掛起、停止、銷毀。
它包括以下常用公用屬性:
屬性名稱 |
說明 |
CurrentContext |
擷取線程正在其中執行的當前上下文。 |
CurrentThread |
擷取當前正在啟動並執行線程。 |
ExecutionContext |
擷取一個 ExecutionContext 對象,該對象包含有關當前線程的各種內容相關的資訊。 |
IsAlive |
擷取一個值,該值指示當前線程的執行狀態。 |
IsBackground |
擷取或設定一個值,該值指示某個線程是否為後台線程。 |
IsThreadPoolThread |
擷取一個值,該值指示線程是否屬於託管線程池。 |
ManagedThreadId |
擷取當前託管線程的唯一識別碼。 |
Name |
擷取或設定線程的名稱。 |
Priority |
擷取或設定一個值,該值指示線程的調度優先順序。 |
ThreadState |
擷取一個值,該值包含當前線程的狀態。 |
2.1.1 線程的標識符
ManagedThreadId是確認線程的唯一識別碼,程式在大部分情況下都是通過Thread.ManagedThreadId來辨別線程的。而Name是一個可變值,在預設時候,Name為一個空值 Null,開發人員可以通過程式設定線程的名稱,但這隻是一個協助工具功能。
2.1.2 線程的優先順序別
.NET為線程設定了Priority屬性來定義線程執行的優先順序別,裡麵包含5個選項,其中Normal是預設值。除非系統有特殊要求,否則不應該隨便設定線程的優先順序別。
成員名稱 |
說明 |
Lowest |
可以將 Thread 安排在具有任何其他優先順序的線程之後。 |
BelowNormal |
可以將 Thread 安排在具有 Normal 優先順序的線程之後,在具有 Lowest 優先順序的線程之前。 |
Normal |
預設選擇。可以將 Thread 安排在具有 AboveNormal 優先順序的線程之後,在具有 BelowNormal優先順序的線程之前。 |
AboveNormal |
可以將 Thread 安排在具有 Highest 優先順序的線程之後,在具有 Normal 優先順序的線程之前。 |
Highest |
可以將 Thread 安排在具有任何其他優先順序的線程之前。 |
2.1.3 線程的狀態
通過ThreadState可以檢測線程是處於Unstarted、Sleeping、Running 等等狀態,它比 IsAlive 屬效能提供更多的特定資訊。
前面說過,一個應用程式定義域中可能包括多個上下文,而通過CurrentContext可以擷取線程當前的上下文。
CurrentThread是最常用的一個屬性,它是用於擷取當前啟動並執行線程。
2.1.4 System.Threading.Thread的方法
Thread 中包括了多個方法來控制線程的建立、掛起、停止、銷毀,以後來的例子中會經常使用。
方法名稱 |
說明 |
Abort() |
終止本線程。 |
GetDomain() |
返回當前線程正在其中啟動並執行當前域。 |
GetDomainId() |
返回當前線程正在其中啟動並執行當前域Id。 |
Interrupt() |
中斷處於 WaitSleepJoin 線程狀態的線程。 |
Join() |
已重載。 阻塞調用線程,直到某個線程終止時為止。 |
Resume() |
繼續運行已掛起的線程。 |
Start() |
執行本線程。 |
Suspend() |
掛起當前線程,如果當前線程已屬於掛起狀態則此不起作用 |
Sleep() |
把正在啟動並執行線程掛起一段時間。 |
2.1.5 開發執行個體
以下這個例子,就是通過Thread顯示當前線程資訊
1 static void Main(string[] args) 2 { 3 Thread thread = Thread.CurrentThread; 4 thread.Name = "Main Thread"; 5 string threadMessage = string.Format("Thread ID:{0}\n Current AppDomainId:{1}\n "+ 6 "Current ContextId:{2}\n Thread Name:{3}\n "+ 7 "Thread State:{4}\n Thread Priority:{5}\n", 8 thread.ManagedThreadId, Thread.GetDomainID(), Thread.CurrentContext.ContextID, 9 thread.Name, thread.ThreadState, thread.Priority);10 Console.WriteLine(threadMessage);11 Console.ReadKey();12 }
運行結果
2.2 System.Threading 命名空間
在System.Threading命名空間內提供多個方法來構建多線程應用程式,其中ThreadPool與Thread是多線程開發中最常用到的,在.NET中專門設定了一個CLR線程池專門用於管理線程的運行,這個CLR線程池正是通過ThreadPool類來管理。而Thread是管理線程的最直接方式,下面幾節將詳細介紹有關內容。
類 |
說明 |
AutoResetEvent |
通知正在等待的線程已發生事件。無法繼承此類。 |
ExecutionContext |
管理當前線程的執行內容。無法繼承此類。 |
Interlocked |
為多個線程共用的變數提供原子操作。 |
Monitor |
提供同步對對象的訪問的機制。 |
Mutex |
一個同步基元,也可用於進程間同步。 |
Thread |
建立並控制線程,設定其優先順序並擷取其狀態。 |
ThreadAbortException |
在對 Abort 方法進行調用時引發的異常。無法繼承此類。 |
ThreadPool |
提供一個線程池,該線程池可用於發送工作項目、處理非同步 I/O、代表其他線程等待以及處理計時器。 |
Timeout |
包含用於指定無限長的時間的常數。無法繼承此類。 |
Timer |
提供以指定的時間間隔執行方法的機制。無法繼承此類。 |
WaitHandle |
封裝等待對共用資源的獨佔訪問的作業系統特定的對象。 |
在System.Threading中的包含了下表中的多個常用委託,其中ThreadStart、ParameterizedThreadStart是最常用到的委託。
由ThreadStart產生的線程是最直接的方式,但由ThreadStart所產生並不受線程池管理。
而ParameterizedThreadStart是為非同步觸發帶參數的方法而設的,在下一節將為大家逐一細說。