C#多執行緒開發-執行緒池03

—阿輝發表於2021-09-06

你好,我是阿輝。

前面2篇文章介紹了執行緒的基礎知識和執行緒同步,下面我們來一起認識學習下,執行緒池的使用。

執行緒池

建立執行緒是昂貴的操作,所以為每個短暫的非同步操作建立執行緒會產生顯著的開銷。一般情況下,都會使用池,也就是執行緒池進行管理。

執行緒池可以成功地適應於任何需要大量短暫的開銷大的資源。事先分配一定的資源,將這些資源放入到資源池中。每次需要新的資源,只需從池中獲取一個,不需要建立新的,當該資源不再被使用時,就將其返回到池中。

在.NET中,執行緒池可以使用ThreadPool型別,受.NET通用語言執行時(CLR)管理。每個CLR都有一個執行緒池例項。ThreadPool型別擁有一個QueueUserWorkItem靜態方法。該方法接收一個委託,代表使用者自定義的一個非同步操作。該方法被呼叫後,委託會進入到內部佇列中,如果執行緒池中沒有任何執行緒,將建立一個新的工作執行緒並將佇列中第一個委託放入到該工作執行緒中。

保持線上程中的操作都是短暫的是非常重要的。不要線上程池中放入長時間執行的操作,或者阻塞工作執行緒。 這將導致所有工作執行緒變的繁忙,從而無法服務使用者操作。這會導致效能問題和非常難以調式的錯誤。

線上程池中,如果停止向其放置新操作時,執行緒池最終會刪除一定時間後過期的不再使用的執行緒。這將釋放所有那些不再的系統資源。

執行緒池的用途是執行執行時間短的操作。使用執行緒池可以減少並行度耗費及節省作業系統資源。

執行緒池中的工作執行緒都是後臺執行緒。這意味著當所有的前臺執行緒(包括主執行緒)完成後,所有的後臺執行緒將停止工作。

執行緒池中非同步的使用

    class Program
    {
        private delegate string RunOnThreadPool(out int threadId);                  //宣告委託

        private static void Callback(IAsyncResult ar)
        {
            Console.WriteLine("觸發回撥");
            Console.WriteLine("非同步狀態:" + ar.AsyncState);
            Console.WriteLine("是否是執行緒池的執行緒:" + Thread.CurrentThread.IsThreadPoolThread);
            Console.WriteLine("ThreadId:" + Thread.CurrentThread.ManagedThreadId);
        }

        private static string Test(out int threadId)
        {
            Console.WriteLine("Test開始");
            Console.WriteLine("是否是執行緒池的執行緒:" + Thread.CurrentThread.IsThreadPoolThread);
            Thread.Sleep(TimeSpan.FromSeconds(2));
            threadId = Thread.CurrentThread.ManagedThreadId;
            return "ThreadId:" + threadId;
        }

        static void Main(string[] args)
        {   
            int threadId=0;
            RunOnThreadPool poolDelegate=Test;
            var t=new Thread(()=>Test(out threadId));
            t.Start();
            t.Join();

            Console.WriteLine("Thread ID="+threadId);

            IAsyncResult ar=poolDelegate.BeginInvoke(out threadId,Callback,"測試是否可以回撥");
            ar.AsyncWaitHandle.WaitOne();

            string result=poolDelegate.EndInvoke(out threadId,ar);

            Console.WriteLine("ID:"+threadId);
            Console.WriteLine("結果:"+result);

            Console.ReadKey();
        }       
    }

執行後可以看到實際的顯示結果。

輸出結果

由於執行緒的建構函式只能接受一個無任何返回結果的方法,所以這裡使用了lambda表示式來將對Test方法的呼叫包起來。

上面是一個很標準的線上程池中使用委託的例子,也可以學習到具體執行緒池的應用。可以看到當第一次執行緒池中沒有執行緒時,列印出來執行緒10不線上程中,當第二次線上程池中時,後面非同步回撥顯示出來的結果就是再次呼叫的執行緒11。

BeginInvoke方法接受一個回撥函式,該回撥函式會在非同步操作完成後會被呼叫,並且一個使用者自定義的狀態會傳給該回撥函式。該狀態通常用於區分非同步呼叫,是一個實現了IAsyncResult介面的result物件。BeginInvoke立即返回結果,當執行緒池中的工作執行緒在執行非同步操作時,仍允許繼續其他工作,可以通過result物件的IsCompleted屬性輪詢結果。當操作完成後,會得到一個結果,可以通過委託呼叫EndInvoke方法,將IAsyncResult物件傳遞給委託引數。

上面這句話其實主要是講解委託線上程池中的應用,如果你想得到某個執行緒的返回結果,就得使用這種非同步委託來實現。

線上程池中使用委託時,呼叫EndInvoke方法是非常重要的。該方法會將任何未處理的異常拋回到呼叫執行緒中。當使用這種非同步API時,請確保始終呼叫Begin和End方法。

上面使用的Begin/End方法和.NET中的IAsyncResult物件等方式被稱為非同步程式設計模型(APM模式),這樣的方法叫非同步方法。

執行緒池中還有一個有用的方法:ThreadPool.RegisterWaitForSingleObject。該方法允許我們將回撥函式放入執行緒池中的佇列中。當提供的等待事件處理器收到訊號或發生超時時,該回撥函式將被呼叫。

線上程池中使用BackgroundWorker元件,可以顯示地指出後臺工作執行緒支援取消操作及操作進度的通知。此時可以使用事件語法。

事件表示了一些通知的源或當通知到達時會有所響應的一系列訂閱者。

這種就是基於事件的非同步模式(EAP),就是啟動一個非同步操作然後訂閱給不同的事件,這些事件在該操作執行時會被觸發。

小寄語

人生短暫,我不想去追求自己看不見的,我只想抓住我能看的見的。

原創不易,給個關注。

我是阿輝,感謝您的閱讀,如果對你有幫助,麻煩點贊、轉發 謝謝。

相關文章