本筆記摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/18/ThreadPool.html,記錄一下學習過程以備後續查用。 一、線程池基礎 首先,創建和銷毀線程是一個要耗費大量時間的過程,其次,太多的線程也會浪費記憶體資源,所以通過Thread類來創 ...
本筆記摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/18/ThreadPool.html,記錄一下學習過程以備後續查用。
一、線程池基礎
首先,創建和銷毀線程是一個要耗費大量時間的過程,其次,太多的線程也會浪費記憶體資源,所以通過Thread類來創建過多的線程反而有損於性能。為了改善這樣
的問題 ,.NET中就引入了線程池。
線程池形象的表示就是存放應用程式中使用的線程的一個集合(就是放線程的地方,這樣線程都放在一個地方就好管理了)。
CLR初始化時,線程池中是沒有線程的,在內部, 線程池維護了一個操作請求隊列。當應用程式想執行一個非同步操作時,就調用一個方法,將一個任務放到線程池
的隊列中,線程池代碼從隊列中提取任務,將這個任務委派給一個線程池線程去執行,當線程池線程完成任務時,線程不會被銷毀,而是返回到線程池中,等待響應另
一個請求。由於線程不被銷毀, 這樣就可以避免因為創建線程所產生的性能損失。
MSDN表述:
“線程池經常用在伺服器應用程式中,每一個新進來的需求被分配給一個線程池中的線程,這樣該需求能被非同步的執行,沒有阻礙主線程或推遲後繼需求的處理。”
註意:通過線程池創建的線程預設為後臺線程,優先順序預設為Normal。
二、通過線程池的工作者線程實現非同步
2.1創建工作者線程的方法
public static bool QueueUserWorkItem (WaitCallback callback);
public static bool QueueUserWorkItem(WaitCallback callback, Object state);
這兩個方法向線程池的隊列添加一個工作項(work item)以及一個可選的狀態數據,然後,這兩個方法就會立即返回。
工作項其實就是由callback參數標識的一個方法,該方法將由線程池線程執行。同時寫的回調方法必須匹配System.Threading.WaitCallback委托類型,定義為:
public delegate void WaitCallback(Object state);
下麵演示如何通過線程池線程來實現非同步調用:
class Program { static void Main(string[] args) { #region 通過線程池的工作者線程實現非同步 //設置線程池中工作者線程最大數量為1000,I/O線程最大數量為1000。 ThreadPool.SetMaxThreads(1000, 1000); Console.WriteLine("Main thread: queue an asynchronous method."); PrintMessage("Main thread start."); //把工作項添加到隊列中,此時線程池會用工作者線程去執行回調方法。 ThreadPool.QueueUserWorkItem(AsyncMethod); Console.Read(); #endregion } /// <summary> /// 列印線程池信息 /// </summary> /// <param name="data"></param> private static void PrintMessage(string data) { // 獲得線程池中可用的工作者線程數量及I/O線程數量 ThreadPool.GetAvailableThreads(out int workThreadNumber, out int ioThreadNumber); Console.WriteLine("{0}\n CurrentThreadId is:{1}\n CurrentThread is background:{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is:{4}\n", data, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsBackground.ToString(), workThreadNumber.ToString(), ioThreadNumber.ToString()); } /// <summary> /// 非同步方法:必須匹配WaitCallback委托 /// </summary> /// <param name="state"></param> private static void AsyncMethod(object state) { Thread.Sleep(1000); PrintMessage("Asynchoronous method."); Console.WriteLine("Asynchoronous thread has worked."); } }
運行結果如下:
從結果中可以看出,線程池中的可用的工作者線程少了一個,用去執行回調方法了。
ThreadPool.QueueUserWorkItem(WaitCallback callback,Object state) 方法可以把object對象作為參數傳送到回調函數中,使用方法與
ThreadPool.QueueUserWorkItem(WaitCallback callback)類似,這裡就不列出了。
2.2 協作式取消
.NET Framework提供了取消操作的模式, 這個模式是協作式的。為了取消一個操作,首先必須創建一個System.Threading.CancellationTokenSource對象。
下麵代碼演示協作式取消的使用,主要實現當用戶在控制台敲下回車鍵後就停止數數方法。
class Program { static void Main(string[] args) { #region 協作式取消 ThreadPool.SetMaxThreads(1000, 1000); Console.WriteLine("Main thread run."); PrintMessage("Start"); Run(); Console.ReadKey(); #endregion } /// <summary> /// 列印線程池信息 /// </summary> /// <param name="data"></param> private static void PrintMessage(string data) { //獲得線程池中可用的工作者線程數量及I/O線程數量 ThreadPool.GetAvailableThreads(out int workThreadNumber, out int ioThreadNumber); Console.WriteLine("{0}\n CurrentThreadId is:{1}\n CurrentThread is background:{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is:{4}\n", data, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsBackground.ToString(), workThreadNumber.ToString(), ioThreadNumber.ToString()); } /// <summary> /// 運行工作者線程(包含協作式取消) /// </summary> private static void Run() { CancellationTokenSource cts = new CancellationTokenSource(); //這裡是用Lambda表達式的寫法,效果一樣。 //ThreadPool.QueueUserWorkItem(obj => Count(cts.Token, 1000)); ThreadPool.QueueUserWorkItem(Callback, cts.Token); Console.WriteLine("Press enter key to cancel the operation.\n"); Console.ReadLine(); //傳達取消請求 cts.Cancel(); } /// <summary> /// 回調函數 /// </summary> /// <param name="state"></param> private static void Callback(object state) { Thread.Sleep(1000); PrintMessage("Asynchoronous method start."); CancellationToken token = (CancellationToken)state; Count(token, 1000); } /// <summary> /// 數數 /// </summary> /// <param name="token"></param> /// <param name="countTo"></param> private static void Count(CancellationToken token, int countTo) { for (int i = 1; i <= countTo; i++) { if (token.IsCancellationRequested) { Console.WriteLine("Count is canceled."); break; } Console.WriteLine(i); Thread.Sleep(300); } Console.WriteLine("Count has done."); } }
運行結果如下:
三、使用委托實現非同步
涉及術語解釋--非同步編程模型:
APM 非同步編程模型(Asynchronous Programming Model)
EAP 基於事件的非同步編程模式(Event-based Asynchronous Pattern)
TAP 基於任務的非同步編程模式(Task-based Asynchronous Pattern)
通過調用ThreadPool的QueueUserWorkItem方法來來啟動工作者線程非常方便,但委托WaitCallback指向的是帶有一個參數的無返回值的方法。如果我們實際操作中
需要有返回值,或者需要帶有多個參數, 這時通過這樣的方式就難以實現了。 為瞭解決這樣的問題,我們可以通過委托來建立工作這線程。
下麵代碼演示使用委托實現非同步:
class Program { //使用委托實現非同步,是使用了非同步編程模型APM。 private delegate string ThreadDelegate(); static void Main(string[] args) { #region 使用委托實現非同步 ThreadPool.SetMaxThreads(1000, 1000); PrintMessage("Main thread start."); //實例化委托 ThreadDelegate threadDelegate = new ThreadDelegate(AsyncMethod); //非同步調用委托 IAsyncResult result = threadDelegate.BeginInvoke(null, null); //獲取結果並列印 string returnData = threadDelegate.EndInvoke(result); Console.WriteLine(returnData); Console.ReadLine(); #endregion } /// <summary> /// 非同步方法 /// </summary> /// <returns></returns> private static string AsyncMethod() { Thread.Sleep(1000); PrintMessage("Asynchoronous method."); return "Method has completed."; } }
運行結果如下:
四、任務
同樣,任務的引入也是為瞭解決通過ThreadPool.QueueUserWorkItem中限制的問題。
下麵代碼演示通過任務來實現非同步:
4.1 使用任務來實現非同步
class Program { static void Main(string[] args) { #region 使用任務實現非同步 ThreadPool.SetMaxThreads(1000, 1000); PrintMessage("Main thread start."); //調用構造函數創建Task對象 Task<int> task = new Task<int>(n => AsyncMethod((int)n), 10); //啟動任務 task.Start(); //等待任務完成 task.Wait(); Console.WriteLine("The method result is: " + task.Result); Console.ReadLine(); #endregion } /// <summary> /// 非同步方法 /// </summary> /// <param name="n"></param> /// <returns></returns> private static int AsyncMethod(int n) { Thread.Sleep(1000); PrintMessage("Asynchoronous method."); int sum = 0; for (int i = 1; i < n; i++) { //運算溢出檢查 checked { sum += i; } } return sum; } }
運行結果如下:
4.2 取消任務
如果要取消任務, 同樣也可以CancellationTokenSource對象來取消。
下麵代碼演示取消一個任務:
class Program { static void Main(string[] args) { #region 取消任務 ThreadPool.SetMaxThreads(1000, 1000); PrintMessage("Main thread start."); CancellationTokenSource cts = new CancellationTokenSource(); //調用構造函數創建Task對象,將一個CancellationToken傳給Task構造器從而使Task和CancellationToken關聯起來。 Task<int> task = new Task<int>(n => AsyncMethod(cts.Token, (int)n), 10); //啟動任務 task.Start(); //延遲取消任務 Thread.Sleep(3000); //取消任務 cts.Cancel(); Console.WriteLine("The method result is: " + task.Result); Console.ReadLine(); #endregion } /// <summary> /// 非同步方法 /// </summary> /// <param name="ct"></param> /// <param name="n"></param> /// <returns></returns> private static int AsyncMethod(CancellationToken ct, int n) { Thread.Sleep(1000); PrintMessage("Asynchoronous method."); int sum = 0; try { for (int i = 1; i < n; i++) { //當CancellationTokenSource對象調用Cancel方法時,就會引起OperationCanceledException異常, //通過調用CancellationToken的ThrowIfCancellationRequested方法來定時檢查操作是否已經取消, //這個方法和CancellationToken的IsCancellationRequested屬性類似。 ct.ThrowIfCancellationRequested(); Thread.Sleep(500); //運算溢出檢查 checked { sum += i; } } } catch (Exception e) { Console.WriteLine("Exception is:" + e.GetType().Name); Console.WriteLine("Operation is canceled."); } return sum; } }
運算結果如下:
4.3 任務工廠
同樣也可以通過任務工廠TaskFactory類型來實現非同步操作。
class Program { static void Main(string[] args) { #region 使用任務工廠實現非同步 ThreadPool.SetMaxThreads(1000, 1000); Task.Factory.StartNew(() => PrintMessage("Main thread.")); Console.Read(); #endregion } /// <summary> /// 列印線程池信息 /// </summary> /// <param name="data"></param> private static void PrintMessage(string data) { //獲得線程池中可用的工作者線程數量及I/O線程數量 ThreadPool.GetAvailableThreads(out int workThreadNumber, out int ioThreadNumber); Console.WriteLine("{0}\n CurrentThreadId is:{1}\n CurrentThread is background:{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is:{4}\n", data, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsBackground.ToString(), workThreadNumber.ToString(), ioThreadNumber.ToString()); } }
運行結果如下: