20181104_C#執行緒之Thread_ThreadPool_使用Thread實現回到和帶引數的回撥
阿新 • • 發佈:2018-11-04
C# .net Framework多執行緒演變路徑:
1.0 1.1 時代使用Thread
2.0 時代使用ThreadPool
3.0 時代使用Task
4.0 時代使用Parallel
4.5 時代使用 async/awit
一. DoSomethingLong方法如下:
/// <summary> /// 一個比較耗時耗資源的私有方法 /// </summary> /// <param name="name"></param> private void DoSomethingLong(string name) { Console.WriteLine($"****************DoSomethingLong {name} Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); long lResult = 0; for (int i = 0; i < 1000000000; i++) { lResult += i; } //Thread.Sleep(2000); Console.WriteLine($"****************DoSomethingLong {name} End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************"); }
二. 使用Thread
a) 下面程式碼演示, 如何使用Thread來啟動一個執行緒
//public Thread(ThreadStart start, int maxStackSize); //maxStackSize 表示指定這個執行緒最大可以使用多少記憶體空間, 一般不用設定 ThreadStart threadStart = () => this.DoSomethingLong("btnThreads_Click"); Thread thread = new Thread(threadStart); thread.Start();
b) Thread的一些其它api
i. thread.Suspend();//(棄用)執行緒掛起, 使執行緒暫停執行; 已過期, 不推薦使用, 會導致死鎖, 因為執行緒的執行的時候, 是會佔用資源的, 雖然手動讓執行緒暫停執行了, 但是它佔用的資源是不會釋放的 ii. thread.Resume();//(棄用)喚醒執行緒, 使掛起的執行緒重新開始執行, 和Suspend()對應 iii. thread.Abort(); //執行緒終止 try { thread.Abort();//銷燬,方式是拋異常 也不建議再使用 不一定及時/有些動作發出收不回來(比如子執行緒向資料庫發出一個查詢命令, 此時命令已經發出, 但是資料庫還沒有返回來值, 但是主執行緒強制子執行緒停止了, 然後資料庫返回來的值就沒有人接收了) //就像你正在跑步, 人後旁邊有人說停; 你從聽到停, 到真正的停下來, 還是處於跑的狀態 // 使用abort一定要使用try catch來處理 } catch (Exception) { // Thread.ResetAbort();//取消Abort異常, 然後繼續計算 } //執行緒等待 iv. thread.Join();//當前執行緒等待thread完成, 當前執行緒就是誰執行這句話, 誰就是當前執行緒, 在這裡當前執行緒就是主執行緒了, 因為主執行緒在執行這句話 thread.Join(500);//最多等500; 當前執行緒等待500毫秒 Console.WriteLine("等待500ms"); // ThreadState.Running //啟動執行緒 while (thread.ThreadState != ThreadState.Stopped) { Thread.Sleep(100);//當前執行緒 休息100ms } v. 關於jion和sleep //jion是實實在在的等待, 佔據cpu; 像上面的示例, thread.jion(500), 那麼這個時候, 其實thread還是在做自己的事情. jion就會在這裡等一段時間(或者等thread把活幹完); 此時是有兩個執行緒併發執行的; 一個thread的執行緒, 一個jion的執行緒 //sleep表示睡眠, 把當前執行緒的上下文儲存著, 把cpu的時間片交出去, cpu可以去處理其他事情 vi. 前臺執行緒和後臺執行緒的區別 ////Console.WriteLine(thread.IsBackground); ////預設是前臺執行緒,啟動之後一定要完成任務的,阻止程序退出; 也就是說, 就算你的應用程式被關閉了, 但是前臺執行緒還是要堅持把它的事情做完之後才會停止自己 ////thread.IsBackground = true;//指定後臺執行緒:隨著程序退出, 也就是說程式關閉後, 後臺執行緒也停止了 vii. 執行緒優先順序: thread.Priority = ThreadPriority.Highest;//執行緒優先順序 ////CPU會優先執行標記為Highest的執行緒, 但是並不代表說Highest就一定最先把事情處理完, 只能說CPU會優先為這個執行緒分配時間片
三. 使用ThreadPool
a) 推出ThreadPool的原因:
i. 在thread中提供了太多的API, 又是掛起, 又是終止, 又是休眠, 又是優先順序, 各種各樣亂七八糟, 但是又不是真真正正的真的能準確的操控 ii. 於是就到2.0之後,Thread就被替換成了ThreadPool, 把所有的該簡化的都簡化了,什麼API都沒有了. 也沒有銷燬, 也沒有掛起, 更沒有暫停; 但是也可以將執行緒進行重用, 避免重複的建立和銷燬, 執行緒被使用完之後, 會放回池子, 下次繼續使用
b) 使用執行緒池ThreadPool的方式啟動多執行緒, 程式碼如下:
//池→容器; 執行緒池就是執行緒的容器, 當在一個系統中, 反覆的使用不同的資源的時候, 但是這些資源使用完需要二次建立(銷燬)的成本太高的時候, 就要考慮使用池技術 //池技術就是享元模式的精華 //QueueUserWorkItem佇列使用者工作項, 將使用者的工作項, 放入到佇列彙總 //QueueUserWorkItem從執行緒池的方式來啟動多執行緒; 這可能是啟動多執行緒最簡單的一種方式了 ThreadPool.QueueUserWorkItem(t => this.DoSomethingLong("btnThreadPool_Click")); ThreadPool.QueueUserWorkItem(t => this.DoSomethingLong("btnThreadPool_Click"));
c) 設定/獲取 ThreadPool中最大和最小執行緒數量:
{ //對執行緒加以限制 workerThreads→執行緒池中最大的工作執行緒資料 //workerThreads →執行緒池中的最大執行緒數, 這個是工作執行緒, 平時啟動的執行緒一般都是基於工作執行緒的, 如果超過了將會被排隊 //completionPortThreads表示執行緒池中非同步i/o執行緒的最大數目 ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads); Console.WriteLine($"GetMaxThreads workerThreads={workerThreads} completionPortThreads={completionPortThreads}"); } { //執行緒池內最小執行緒數; 預設情況下, 最小的執行緒數, 好像是跟CPU有關; 比如4核8執行緒的, 那麼這裡就是8和8; 我的就是4和4 ThreadPool.GetMinThreads(out int workerThreads, out int completionPortThreads); Console.WriteLine($"GetMinThreads workerThreads={workerThreads} completionPortThreads={completionPortThreads}"); } // 有get就有set 設定最大執行緒數 ThreadPool的最大執行緒數也會影響這Task的執行緒 ThreadPool.SetMaxThreads(16, 16); // 設定最小執行緒數 ThreadPool.SetMinThreads(8, 8); //實際操作來看, 最小的會受到影響 //設定完成後, 再次列印一次 { ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads); Console.WriteLine($"GetMaxThreads workerThreads={workerThreads} completionPortThreads={completionPortThreads}"); } { ThreadPool.GetMinThreads(out int workerThreads, out int completionPortThreads); Console.WriteLine($"GetMinThreads workerThreads={workerThreads} completionPortThreads={completionPortThreads}"); }
d) 在ThreadPool中實現執行緒等待
////ThreadPool啥API都沒有, 那麼如何在ThreadPool中等待執行緒完成才往下執行呢? // 可以使用 ManualResetEvent(手動重啟事件)類, 這個類包含了一個bool屬性, 如果在初始化這個類的時候, 將其初始化為false, 則這個類例項的的WaitOne()方法將會被阻塞, 一直阻塞, 直到他的bool屬性變成true; 當然, 可以通過ManualResetEvent的set方法使其變成 true ; 當然這裡也可以自己定義一個變數來實現, 但是ManualResetEvent是執行緒安全的 // false--WaitOne等待--Set--true--WaitOne直接過去 // true--WaitOne直接過去--ReSet--false--WaitOne等待 ManualResetEvent manualResetEvent = new ManualResetEvent(false); //初始化ManualResetEvent類中的變數為false ThreadPool.QueueUserWorkItem(t => { Console.WriteLine("即將開始執行DoSomethingLong函式"); this.DoSomethingLong("btnThreadPool_Click"); Console.WriteLine("執行DoSomethingLong函式完畢"); manualResetEvent.Set(); //將訊號設定為true //manualResetEvent.Reset(); //將訊號設定為false }); manualResetEvent.WaitOne(); //阻塞當前執行緒; 如果當前manualResetEvent在主執行緒建立的, 那麼就會阻塞主執行緒 但是要注意一般來說,不要阻塞執行緒池的執行緒
四. 使用Thread完成回撥和帶返回值的回撥
a) 使用Thread完成回撥, 程式碼如下:
/// <summary> /// 演示執行緒的回撥; 啟動子執行緒計算--完成委託後,該執行緒去執行後續回撥委託 ; /// </summary> /// <param name="act">第一個委託是你真的想要執行的這個方法</param> /// <param name="callback">第二委託是當你執行完第一個方法之後, 想要執行的回撥, 其實就是在一個執行緒內將兩個方法並列執行了一次</param> private void ThreadWithCallback(Action act, Action callback) { Thread thread = new Thread(() => { act.Invoke(); callback.Invoke(); }); thread.Start(); }
呼叫方法如下:
private void btnThreads_Click(object sender, EventArgs e) { this.ThreadWithCallback(() => Console.WriteLine($"這裡是action {Thread.CurrentThread.ManagedThreadId.ToString("00")}") , () => Console.WriteLine($"這裡是callback {Thread.CurrentThread.ManagedThreadId.ToString("00")}")); }
b) 使用Thread完成帶返回值的回撥, 程式碼如下:
/// <summary> /// 帶返回值的非同步呼叫; 帶返回的非同步呼叫 需要獲取返回值 (會卡介面的) /// </summary> /// <typeparam name="T"></typeparam> /// <param name="func"></param> /// <returns></returns> private Func<T> ThreadWithReturn<T>(Func<T> func) { #region 錯誤的寫法 //T t ; //Thread thread = new Thread(() => //{ // t = func.Invoke(); //這裡還沒有執行的時候, 它已經返回了 //}); //thread.Start(); // return t; #endregion T t = default(T); Thread thread = new Thread(() => { t = func.Invoke(); }); thread.Start(); return () => { // while (thread.ThreadState != ThreadState.Stopped) { Thread.Sleep(200); } thread.Join(); return t; }; }
呼叫方法如下:
private void withReturn_Click(object sender, EventArgs e) { Func<int> func = this.ThreadWithReturn<int>(() => { Thread.Sleep(2000); return DateTime.Now.Millisecond; }); Console.WriteLine("上面是非同步呼叫, 直接就會列印這句話, 這句話不會等待2秒"); int iResult = func.Invoke(); Console.WriteLine(iResult); }