1. CLR線程池簡介
1.1 CLR為什麼支援線程池
1.2 線程池ThreadPool管理線程
2. 線程執行內容
2.1 線程執行內容簡介
2.2 一個簡單樣本
3. 線程池常見應用情景樣本
3.1 將一個線程添加至線程池中(向線程傳遞參數)
3.2 協作式取消
4. Task簡介
5. Task編程基礎
6. 定時器Timer
<1>. CLR線程池簡介
1.1 CLR為什麼支援線程池?
上一篇中講到如果在一個應用程式中啟動了多個線程的話,顯然是會影響到程式的運行效率,一種很直觀的想法是:如果一個線程完成了任務,我們並不在記憶體中銷毀這個線程的執行個體,而是將這個線程進入空閑狀態,如果又來了一個請求,可以不用另外建立新線程,這樣顯然能夠帶來效能上的提升,在.net中,CLR為我們實現了線程池,它可以看作是線程的一個集合。每個CLR都存在一個全域的線程池,供所有的AppDomain共用。
1.2 線程池ThreadPool管理線程
System.Threading.ThreadPool中提供了查詢和設定線程池性質的的靜態方法。
public static bool SetMaxThreads(int workerThreads, int completionPortThreads); // 設定線程池中線程數量的最大值,但是永遠不要折磨做,容易發生死結等情況
public static bool SetMinThreads(int workerThreads, int completionPortThreads); // 設定線程池中線程數量的最小值
public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads); // 返回線程池支援的最大線程數和當前活動的線程的差值
<2>. 線程執行的上下文及其流轉
2.1 線程執行內容簡介
線程的執行內容包含安全設定,宿主設定,線程上下文資料(System.Runtime.Remoting.Messaging.CallContext的LogicalSetData方法)等資訊。當一個線程使用另外的線程執行任務時,前者的線程執行內容將流轉至後者的線程執行內容中。在c#中類型System.Threading.ExecutionContext來控制線程執行內容的流轉:
public static AsyncFlowControl SuppressFlow(); // 阻止( Suppress本意是抑制)線程執行內容的流轉
public static bool IsFlowSuppressed(); // 判斷當先線程執行內容是否被阻止
2.2 一個簡單樣本
static void Main(string[] args) { // 見一些資料儲存在Main線程的執行內容中 CallContext.LogicalSetData("myname", "xuqiang"); // 線程執行內容流轉,所以這裡是能夠得到myname的值的 ThreadPool.QueueUserWorkItem ( state => { // 將輸出xuqiang Console.WriteLine("MyName :" + CallContext.LogicalGetData("myname")); } ); // 現在阻止線程內容相關的流動 ExecutionContext.SuppressFlow(); // 下面的線程將無法訪問Main線程的上下文 ThreadPool.QueueUserWorkItem ( state => { // 不會輸出xuqiang Console.WriteLine("MyName :" + CallContext.LogicalGetData("myname")); } ); // 回複上下文流轉 ExecutionContext.RestoreFlow(); // 等待線程執行完畢 Thread.Sleep(1000); Console.WriteLine("Press any key..."); Console.ReadKey();
}
通過上面的建立樣本,我們能夠明確,
1. 如何向線程池中添加背景工作執行緒:
通過調用QueueUserWorkItem,該靜態方法存在兩個重載方法:
public static bool QueueUserWorkItem(WaitCallback callBack); // 不需要傳遞參數
public static bool QueueUserWorkItem(WaitCallback callBack, object state); // 需要向線程傳遞參數
2. 通過函數SuppressFlown能夠阻止線程內容相關的流轉,通過RestoreFlow能夠恢複線程上下文
3. 通過Thread.Sleep函數能夠阻塞調用Thread.Sleep的線程(這裡是指Main線程)
<3>. 線程池常見應用情境樣本
3.1 將一個線程添加至線程池中(向線程中傳遞參數)
static void Main(string[] args) { Console.WriteLine("Main thread: queuing an asychronous operation"); // 向線程池中添加一個背景工作執行緒,同時傳遞參數5 ThreadPool.QueueUserWorkItem(ComputeOp, 5); Console.WriteLine("Main thread: Doing other work here.."); // 將main線程休眠一段時間,以保證computeOp執行完成 Thread.Sleep(10000); // 10秒 Console.WriteLine("Hit <Enter> to end this program"); Console.ReadKey(); } private static void ComputeOp(object state) { Console.WriteLine("CompiteOp: state is " + state); // 類比工作1s Thread.Sleep(1000); // 這個線程返回到線程池中,將等待另外的任務
}
3.2 協作式取消
之所以稱之為“協作式取消”,是講一個線程T如果想要獲得被取消的能力的話,那麼T需要接受一個類型為CancellationToken參數(該類型存在於.net 4.0中,3.5中不存在),在T中通過查詢CancellationToken的IsCancellationRequested屬性來判斷是否已經被取消。下面結合這個情境來分析這個過程中使用到的類CancellationTokenSource和CancellationToken:
public sealed class CancellationTokenSource : IDisposable
{
public CancellationTokenSource(); // 預設建構函式
public bool IsCancellationRequested { get; } // 查詢線程是否已經被取消
public CancellationToken Token { get; } // 構造好了CancellationTokenSource通過該方法得到CancellationToken執行個體,然後傳遞給需要實現協作式取消的線程T
public void Cancel(); // 通過該方法取消T線程
// 取消線程T的重載版本,可以通過CancellationTokenSource的CancellationToken屬性的Register註冊委託,在Cancel方法調用後執行。如
// 果throwOnFirstException為true的話,那麼在註冊的方法中的任何一個方法拋出未處理異常,Cancel將拋出該異常 ,同時阻止其他回調方法
// 的調用,如果throwOnFirstException為false的話,如果在任何的回調方法中拋出異常不會影響到其他回調方法的調用,未處理的異常將添加
// 至AggregateException中
public void Cancel(bool throwOnFirstException);
public void Dispose(); // 實現IDisposable介面
// 串連另外一組 CancellationToken來建立CancellationTokenSource樣本,例如如果通過var cts = CreateLinkedTokenSource(token1, token2)
// 那麼如果token1或者token2取消,cts將被取消
public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens);
// 重載版本
public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2);
}
public struct CancellationToken
{
public bool IsCancellationRequested { get; }// 得到是否已經存在對於該CancellationToken的取消操作
public static CancellationToken None { get; } // 如果一個背景工作處理序不想被取消的話,可以使用該靜態欄位
// 下面的幾個方法將向 CancellationToken中註冊委託時間,如果CancellationToken被取消,將調用這些方法
public CancellationTokenRegistration Register(Action callback);
public CancellationTokenRegistration Register(Action<object> callback, object state);
// 該方法主要解決在winform,wpf等中,背景工作執行緒如何更新ui線程
public CancellationTokenRegistration Register(Action callback, bool useSynchronizationContext);
....
}
這個樣本示範了如何使用CancellationTokenSource,並向其中註冊回調方法,如何管理異常等特性:
static void Main(string[] args) { Go(); } public static void Go() { // 產生CancellationTokenSource對象 CancellationTokenSource cts = new CancellationTokenSource(); // 向cts中添加回調事件,但token調用cancel方法時,將調用這個方法 cts.Token.Register( WhenCancel1 ); // 將拋出異常 cts.Token.Register(WhenCancel2 ); cts.Token.Register(WhenCancel3); // 向線程隊列中添加一個背景工作執行緒 ThreadPool.QueueUserWorkItem ( o => Count(cts.Token, 1000) ); Console.WriteLine("Press enter to cancel the operation ."); Console.ReadLine(); try { // 如果為true的話,將僅僅調用WhenCancel3方法 // 如果為false將調用這三個回調方法 cts.Cancel(true); } catch (AggregateException ex) { Console.WriteLine(ex.InnerException.ToString()); } } private static void WhenCancel1() { Console.WriteLine("When cancel 1 delegate called."); } private static void WhenCancel2() { // Console.WriteLine("When cancel 3 delegate called."); throw new Exception(); } private static void WhenCancel3() { Console.WriteLine("When cancel 3 delegate called."); } private static void Count(CancellationToken token, Int32 countTo) { for (Int32 count = 0; count < countTo; ++count ) { // 如果被取消 if(token.IsCancellationRequested) { Console.WriteLine("The operation is cancel."); break; } // 列印 Console.WriteLine(count); // 休眠該線程一段時間 Thread.Sleep(200); } Console.WriteLine("Count is done.");
}
下面的樣本將示範如何?一個連結的CancellationTokenSource
public static void Go() { // 產生CancellationTokenSource對象 CancellationTokenSource cts1 = new CancellationTokenSource(); cts1.Token.Register(() => Console.WriteLine("cts1 is cancel")); CancellationTokenSource cts2 = new CancellationTokenSource(); cts2.Token.Register(() => Console.WriteLine("cts2 is cancel")); // 建立連結 CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token); // 如果取消cts1或者是cts2中的一個,那麼linkedCts將被取消 cts1.Cancel(); Console.WriteLine("cts1 cancel = {0}, cts2 cancel = {1}", cts1.IsCancellationRequested, cts2.IsCancellationRequested); Console.WriteLine("Press enter to cancel the operation ."); Console.ReadLine();
}
下一篇繼續...