1. 程式人生 > >20181104_C#執行緒之Thread_ThreadPool_使用Thread實現回到和帶引數的回撥

20181104_C#執行緒之Thread_ThreadPool_使用Thread實現回到和帶引數的回撥

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); 
        }