C#線程學習筆記二:線程池中的工作者線程

    本筆記摘抄自: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>
        /// <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>
        /// <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="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>
        /// <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="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>
        /// <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());
        }
    }

    運行結果如下:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章