您好,登錄后才能下訂單哦!
這篇文章主要講解了“C#異步多線程ThreadPool怎么使用”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“C#異步多線程ThreadPool怎么使用”吧!
ThreadPool 提供的 API 相對于 Thread 是比較少的,在 ThreadPool 中需使用 QueueUserWorkItem 方法,來啟動一個線程
例如:Dosome 是個普通的方法,傳入 QueueUserWorkItem 方法開啟新線程執行此方法
public static void Dosome() { Console.WriteLine($"Task Start ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); } static void Main(string[] args) { Console.WriteLine($"Main 方法開始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); ThreadPool.QueueUserWorkItem(x => Dosome()); Console.WriteLine($"Main 方法結束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); Console.ReadLine(); }
啟動線程,可以看到新開啟了一個子線程 3 執行任務,而主線程 1 并沒有等待子線程 3
在 1.0 時代的 Thread 是沒有線程數量概念的,在 ThreadPool 2.0 時代,線程池線程數量可以通過 SetMaxThreads、SetMaxThreads 方法設置最小最大線程。也可以查看線程池線程數量,以通過 GetMinThreads、GetMaxThreads 方法獲取線程池最小及最大線程數量。
注意:一般不建議設置 ThreadPool 線程數量,這個操作是全局的。二般情況,當線程池線程耗盡,會造成死鎖。
例如:以通過 SetMaxThreads、SetMaxThreads、GetMinThreads、GetMaxThreads 方法來操作查看線程
{ ThreadPool.GetMinThreads(out int workerThreadsMin, out int completionPortThreadsMin);//工作線程,io線程 Console.WriteLine($"【default】最小 workerThreadsMin:{workerThreadsMin} completionPortThreadsMin:{completionPortThreadsMin}"); ThreadPool.GetMaxThreads(out int workerThreadsMax, out int completionPortThreadsMax);//工作線程,io線程 Console.WriteLine($"【default】最大 workerThreadsMax:{workerThreadsMax} completionPortThreadsMax:{completionPortThreadsMax}"); } ThreadPool.SetMinThreads(3, 3); // 設置4其實也不是4,應為本機為邏輯八核,最小也就是這個 ThreadPool.SetMaxThreads(7, 7); { ThreadPool.GetMinThreads(out int workerThreadsMin, out int completionPortThreadsMin);//工作線程,io線程 Console.WriteLine($"【自定義】最小 workerThreadsMin:{workerThreadsMin} completionPortThreadsMin:{completionPortThreadsMin}"); ThreadPool.GetMaxThreads(out int workerThreadsMax, out int completionPortThreadsMax);//工作線程,io線程 Console.WriteLine($"【自定義】最大 workerThreadsMax:{workerThreadsMax} completionPortThreadsMax:{completionPortThreadsMax}"); } ThreadPool.SetMinThreads(5, 5); // 設置4其實也不是4,應為本機為邏輯八核,最小也就是這個 ThreadPool.SetMaxThreads(16, 16); { ThreadPool.GetMinThreads(out int workerThreadsMin, out int completionPortThreadsMin);//工作線程,io線程 Console.WriteLine($"【自定義】最小 workerThreadsMin:{workerThreadsMin} completionPortThreadsMin:{completionPortThreadsMin}"); ThreadPool.GetMaxThreads(out int workerThreadsMax, out int completionPortThreadsMax);//工作線程,io線程 Console.WriteLine($"【自定義】最大 workerThreadsMax:{workerThreadsMax} completionPortThreadsMax:{completionPortThreadsMax}"); }
看了前面 ThreadPool 相關的講解,有小伙伴可能會發現,我們一直沒有說等待線程,那 ThreadPool 有相關的 API 嗎?答案:沒有
但可以通過 ManualResetEvent 方式實現線程等待。一般來說不建議線程等待,二般情況也不建議。應為線程池里面,線程數量有限,寫代碼無意間造成的線程等待沒有釋放,一旦線程池線程耗盡就會形成死鎖。除非非得等待情況,但記得一定要釋放等待,多多檢查代碼。
例如:線程等待,ManualResetEvent 初始化為 false,Set() 方法會設置為 true,WaitOne() 方法會檢查 ManualResetEvent 對象是否為 true,如果不為會一直等待,如果為 true 會直接過去
public static void Dosome() { Console.WriteLine($"Task Start ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); Thread.Sleep(5 * 1000); // 模擬任務耗時 Console.WriteLine($"Task End ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); } static void Main(string[] args) { Console.WriteLine($"Main 方法開始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); ManualResetEvent manualResetEvent = new ManualResetEvent(false); ThreadPool.QueueUserWorkItem(x => { Dosome(); manualResetEvent.Set(); // 會變成 true }); manualResetEvent.WaitOne(); Console.WriteLine($"Main 方法結束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); Console.ReadLine(); }
啟動程序,可以看到主線程 1 等待 子線程 3 執行完成后,在執行了 Main 方法結束代碼
例如:線程耗盡形成死鎖,首先對線程池線程數量進行了限制,最大為 10 個線程。接著我們循環啟動 18 個線程工作,且讓前 18 個線程形成等待。
Console.WriteLine($"Main 方法開始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); ThreadPool.SetMinThreads(4, 4); ThreadPool.SetMaxThreads(10, 10); ThreadPool.GetMinThreads(out int workerThreadsMin, out int completionPortThreadsMin);//工作線程,io線程 Console.WriteLine($"【自定義】最小 workerThreadsMin:{workerThreadsMin} completionPortThreadsMin:{completionPortThreadsMin}"); ThreadPool.GetMaxThreads(out int workerThreadsMax, out int completionPortThreadsMax);//工作線程,io線程 Console.WriteLine($"【自定義】最大 workerThreadsMax:{workerThreadsMax} completionPortThreadsMax:{completionPortThreadsMax}"); ManualResetEvent manualResetEvent = new ManualResetEvent(false); for (int i = 0; i < 20; i++) { int k = i; ThreadPool.QueueUserWorkItem((x) => { Console.WriteLine(k); if (k < 18) { manualResetEvent.WaitOne(); } else { manualResetEvent.Set(); } }); } manualResetEvent.WaitOne(); Console.WriteLine($"Main 方法結束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); Console.ReadLine();
啟動程序,可以看到,當開啟 10 個線程后,程序就已經不再運行了。這是當程循環開啟第 11 個子線程時,發現線程池里面沒有線程了,就會一直等待,這樣一個狀態就是死鎖。
講到現在,細心的小伙伴會發現一直沒有說線程回調,即當子線程執行一個任務完成后,再執行一個任務。其實 Thread 與 ThreadPool 都沒有回調,但是可以創造出 Callback,那就是包一層,如果不行那就再包一層。
例如:創建一個普通方法 ThreadWithCallback 傳入兩個委托參數,一個實際任務,一個 Callback。接著在內部使用 Thread 開啟一個新的線程,執行 action、callback 方法。
private static void ThreadWithCallback(Action action, Action callback) { Thread thread = new Thread(() => { action.Invoke(); callback.Invoke(); }); thread.Start(); } static void Main(string[] args) { Console.WriteLine($"Main 方法開始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); ThreadWithCallback(() => { Console.WriteLine($"action,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); }, () => { Console.WriteLine($"callback,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); }); Console.WriteLine($"Main 方法結束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); Console.ReadLine(); }
啟動程序,可以看到 action 執行后,再執行了 callback
例如:創建一個普通方法 ThreadWithCallback 傳入兩個委托參數,一個實際任務,一個 Callback。接著在內部使用 ThreadPool 開啟一個新的線程,執行 action、callback 方法。
private static void ThreadWithCallback(Action action, Action callback) { ThreadPool.QueueUserWorkItem(x => { action.Invoke(); callback.Invoke(); }); } static void Main(string[] args) { Console.WriteLine($"Main 方法開始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); ThreadWithCallback(() => { Console.WriteLine($"action,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); }, () => { Console.WriteLine($"callback,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); }); Console.WriteLine($"Main 方法結束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); Console.ReadLine(); }
啟動程序,可以看到 action 執行后,再執行了 callback
講到現在,細心的小伙伴會發現一直沒有說線程返回值,在 1.0、2.0 時代的 Thread、ThreadPool 是沒有提供相關 API 的。但是可以創造出來,還是包一層,如果不行那就再包一層。
例如:創建一個普通方法 ThreadWithReturnCallback 返回與入參都是 Func< T >,內部啟用一個 Thread 執行委托,return 一個帶返回值的委托且 Thread 的線程等待放置里面。使用時給 ThreadWithReturnCallback 方法傳入帶返回值的委托即可,因為 ThreadWithReturnCallback 方法返回值也是委托,所以要想獲得結果需要在外部 Invoke 一下,這個 Invoke 操作會卡主線程。
private static Func<T> ThreadWithReturnCallback<T>(Func<T> func) { T t = default(T); Thread thread = new Thread(() => { t = func.Invoke(); }); thread.Start(); return () => { thread.Join(); return t; }; } static void Main(string[] args) { Console.WriteLine($"Main 方法開始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); Func<int> func = ThreadWithReturnCallback<int>(() => { return DateTime.Now.Millisecond; }); int iResult = func.Invoke(); Console.WriteLine(iResult); Console.WriteLine($"Main 方法結束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); Console.ReadLine(); }
例如:創建一個普通方法 ThreadPoolWithReturnCallback 返回與入參都是 Func< T >,使用 QueueUserWorkItem 方法啟動線程執行委托,因為 ThreadPool 本身并未提供線程等待方法,所以這里使用 ManualResetEvent 進行線程等待,return 一個帶返回值的委托且 ManualResetEvent WaitOne 線程等待放置里面。使用時給 ThreadPoolWithReturnCallback 方法傳入帶返回值的委托即可,因為 ThreadPoolWithReturnCallback 方法返回值也是委托,所以要想獲得結果需要在外部 Invoke 一下,這個 Invoke 操作會卡主線程。
private static Func<T> ThreadPoolWithReturnCallback <T>(Func<T> func) { T t = default(T); ManualResetEvent manualResetEvent = new ManualResetEvent(false); ThreadPool.QueueUserWorkItem(x => { t = func.Invoke(); manualResetEvent.Set(); // 會變成 true }); return () => { manualResetEvent.WaitOne(); return t; }; } static void Main(string[] args) { Console.WriteLine($"Main 方法開始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); Func<int> func = ThreadPoolWithReturnCallback <int>(() => { return DateTime.Now.Millisecond; }); int iResult = func.Invoke(); Console.WriteLine(iResult); Console.WriteLine($"Main 方法結束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); Console.ReadLine(); }
在 1.0 時代的 Thread 每次創建實例都會向操作系統申請線程,2.0 時代的 ThreadPool 每次使用 QueueUserWorkItem 都會向線程池拿取線程,并不會向操作系統申請線程。所以,使用 ThreadPool 創建線程的效率是高于 Thread 的。
例如:我們開啟三波線程執行任務,執行相同的任務
static void Main(string[] args) { Console.WriteLine($"Main 方法開始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}\n"); ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"張三,任務處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); }); ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"李四,任務處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); }); ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"王五,任務處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); }); ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"麻溜,任務處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); }); Thread.Sleep(1000);Console.WriteLine(); ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"張三,任務處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); }); ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"李四,任務處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); }); ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"王五,任務處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); }); ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"麻溜,任務處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); }); Thread.Sleep(1000); Console.WriteLine(); ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"張三,任務處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); }); ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"李四,任務處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); }); ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"王五,任務處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}"); }); ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"麻溜,任務處理完成。ThreadId:{Thread.CurrentThread.ManagedThreadId}\n"); }); Thread.Sleep(1000); Console.WriteLine($"Main 方法結束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}"); Console.ReadLine(); }
啟動程序,第一波的時候啟用了 3、4、6、7,第二波重用了第一波的 6、7、第三波重用了第一、第二波的 3、4、5、8。其中未重用的呢,是線程并未回收(回收需要時間),所以未重用。
委托的 BeginInvoke 方法使用的是線程池的線程,在任務執行完成后,子線程時不會被立馬回收的,除非調用 EndInvoke 可以立馬結束子線程回到線程池,利于線程更好的重用。
例如:BeginInvoke 線程不能立馬被回收重用
static void Main(string[] args) { Action<int> action = x => { Console.WriteLine($"我是 {x},Thread:{Thread.CurrentThread.ManagedThreadId}"); }; for (int i = 0; i < 5; i++) { action.BeginInvoke(i,null,null); } Console.ReadLine(); }
啟動線程,并發五次,分別啟用了4、5、7、8、9,五個線程
例如:EndInvoke 線程更好重用,BeginInvoke 方法的第二個參數
static void Main(string[] args) { Action<int> action = x => { Console.WriteLine($"我是 {x},Thread:{Thread.CurrentThread.ManagedThreadId}"); }; for (int i = 0; i < 5; i++) { action.BeginInvoke(i, e => { action.EndInvoke(e); }, null); } Console.ReadLine(); }
啟程序,可以看到并發 5 次只使用了,線程 3 與 8。
感謝各位的閱讀,以上就是“C#異步多線程ThreadPool怎么使用”的內容了,經過本文的學習后,相信大家對C#異步多線程ThreadPool怎么使用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。