1. 程式人生 > 實用技巧 >C# 執行緒 執行緒池 Task的使用介紹

C# 執行緒 執行緒池 Task的使用介紹

從 .NET Framework 4.5 開始,任何使用 async/await 進行修飾的方法,都會被認為是一個非同步方法;實際上,這些非同步方法都是基於佇列的執行緒任務,從你開始使用 Task 去執行一段程式碼的時候,實際上就相當於開啟了一個執行緒,預設情況下,這個執行緒數由執行緒池 ThreadPool 進行管理的。

執行緒:
Thread,由CLR提供,可以提供細粒度的執行緒控制,但是在返回值、多個執行緒並行/序列時候顯得功能不足,需要進一步封裝才行,而且執行緒的開啟、停止都需要時間開銷,亂用的話還造成浪費。
執行緒池:
ThreadPool,由CLR提供,提供粗粒度的執行緒控制,主要用來執行短時間內執行的程式碼片段,一個程序有一個執行緒池,由CLR統一排程。它對執行緒的控制力較少,而且在返回值、多個執行緒並行/序列時候顯也是功能不足。

任務:
Task,對ThreadPool和Thread的包裝,可以根據任務時間長短選擇使用執行緒池還是新的執行緒,通過進一步擴充套件,增加了返回值、多個執行緒並行/序列等功能。它的核心是一個排程器,預設是ThreadPoolTaskScheduler。Task使用的是非同步操作一個執行緒池執行緒

Task和thread很大的一個區別就是,在task中如果有一個阻塞的話,整個task就會被阻塞住,當前的執行緒ID不會改變,在thread中如果有一個阻塞的話,會去執行另外的thread,然後回來執行原來的那個thread,執行緒ID會改變為其他的ID。

ThreadPool是Thread基礎上的一個執行緒池

,目的是減少頻繁建立執行緒的開銷。執行緒很貴,要開新的stack,要增加CPU上下文切換,所以ThreadPool適合頻繁、短期執行的小操作。排程演算法是自適應的,會根據程式執行的模式調整配置,通常不需要自己排程執行緒。另外分為Worker和IO兩個池。IO執行緒對應Native的overlapped io,Win下利用IO完成埠實現非阻塞IO。

Task或者說TPL是一個更上層的封裝,NB之處在於continuation。continuation的意義在於:高效能的程式通常都是跑在IO邊界或者UI事件的邊界上的,TPL的continuation可以更方便的寫這種高scalability的程式碼。Task會根據一些flag,比如是不是long-running來決定底層用Thread還是ThreadPool,另外也做了些細節優化,比如同一個執行緒跑好幾個Task,比如continuation時根據情況讓CPU空轉幾毫秒來等待前置Task結束,都是為了減少CPU上下文切換。

結論:能用Task就用Task,底下都是用的Thread或者ThreadPool。但是要注意細節,比如告訴Task是不是long-running;比如儘量別Wait;再比如IO之後的continuation要儘快結束然後把執行緒還回去,有事開個Worker做,要不然會影響後面的IO,等等。

Task的使用
        /// <summary>
        /// 最簡單的使用方式
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [Route("GetTask")]
        public IActionResult GetTask()
        {
            Console.ForegroundColor = ConsoleColor.Red;

            // 執行一個無返回值的任務
            Task.Run(() => { Console.WriteLine("runing ..."); });

            // 執行一個返回 int 型別結果的任務
            var res1 = Task.Run<int>(() => { return 483; });

            // 宣告一個任務,僅宣告,不執行
            Task t = new Task(() => { Console.WriteLine("宣告"); });

            Console.ResetColor();

            return Ok("test");
        }

        /// <summary>
        /// 使用 TaskFactory 工廠開始非同步任務
        ///使用 TaskFactory 建立並運行了兩個非同步任務,同時把這兩個任務加入了任務列表 tasks 中
        ///然後立即迭代此 tasks 獲取非同步任務的執行結果,使用 TaskFactory 工廠類,可以建立一組人物,然後依次執行它們
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [Route("GetTask2")]
        public IActionResult GetTask2()
        {
            Console.ForegroundColor = ConsoleColor.Red;

            List<Task<int>> tasks = new List<Task<int>>();

            TaskFactory factory = new TaskFactory();

            tasks.Add(factory.StartNew<int>(() => { Console.WriteLine("t1"); return 1; }));
            tasks.Add(factory.StartNew<int>(() => { Console.WriteLine("t2"); return 2; }));
            tasks.Add(factory.StartNew<int>(() => { Console.WriteLine("t3"); return 3; }));

            tasks.ForEach(t => Console.WriteLine("Task:{0}", t.Result));

            Console.ResetColor();

            return Ok("test2");
        }   

        /// <summary>
        ///  處理 Task 中的異常
        ///非同步任務中發生異常會導致任務丟擲 TaskCancelException 的異常,僅表示任務退出,程式應當捕獲該異常;然後,立即呼叫 Task 進行狀態判斷,獲取內部異常 上面的程式碼模擬了 Task 內部發生的異常,並捕獲了異常
        ///通常情況下,推薦使用 Task 的任務狀態判斷以進行下一步的任務處理(如果需要),如果僅僅是簡單的執行一個非同步任務,直接捕獲異常即可,這裡使用了狀態判斷,如果任務已完成,則列印一則訊息:IsCompleted;很明顯,在上面的程式碼中,此 “IsCompleted” 訊息並不會被列印到控制檯
        ///注意,這裡使用了 task.IsCompletedSuccessfully 而不是 task.IsCompleted,這兩者的區別在於,前者只有在任務正常執行完成,無異常,無中途退出指令的情況下才會表示已完成,而 task.IsCompleted 則僅僅表示“任務完成” 
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [Route("GetTask3")]
        public IActionResult GetTask3()
        {
            Console.ForegroundColor = ConsoleColor.Red;

            var task = Task.Run(() =>
            {
                Console.WriteLine("SimpleTask");
                Task.Delay(1000).Wait();
                throw new Exception("SimpleTask Error");
            });

            try
            {
                task.Wait();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            if (task.IsCompletedSuccessfully)//任務成功
            {
                Console.WriteLine("IsCompletedSuccessfully");
            }

            if (task.IsCompleted)//任務完成
            {
                Console.WriteLine("IsCompleted");
            }


            Console.ResetColor();

            return Ok("test2");
        }   

執行緒的使用

         Thread t = new Thread(()=> {
                Console.WriteLine("Starting...");
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine(i);
                }
            });
            t.Start();

        static void Main(string[] args)
        {
            int b = 10;
            string c = "主執行緒";
            Thread t = new Thread(()=> PrintNumbers(b,c));

            t.Start();

        }
        static void PrintNumbers(int count,string name)
        {
            for (int i = 0; i < count; i++)
            {
                Console.WriteLine("name:{0},i:{1}",name,i);
            }
        }

執行緒池的使用

class Program
    {
        static void Main(string[] args)
        {
            WaitCallback wc1 = s =>{
                Console.WriteLine("執行緒ID:{0},開始執行", Thread.CurrentThread.ManagedThreadId);
                Stopwatch stw = new Stopwatch();
                stw.Start();
                long result = SumNumbers(10000000);
                stw.Stop();
                Console.WriteLine("執行緒ID:{0},執行完成,執行結果:{1},執行用時{2},",Thread.CurrentThread.ManagedThreadId,result,stw.ElapsedMilliseconds);
            };
            WaitCallback wc2 = s => {
                Console.WriteLine("執行緒ID:{0},開始執行", Thread.CurrentThread.ManagedThreadId);
                Stopwatch stw = new Stopwatch();
                stw.Start();
                long result = SumNumbers(10000000);
                stw.Stop();
                Console.WriteLine("執行緒ID:{0},執行完成,執行結果:{1},執行用時{2},", Thread.CurrentThread.ManagedThreadId, result, stw.ElapsedMilliseconds);
            };
            WaitCallback wc3 = s => {
                Console.WriteLine("執行緒ID:{0},開始執行", Thread.CurrentThread.ManagedThreadId);
                Stopwatch stw = new Stopwatch();
                stw.Start();
                long result = SumNumbers(10000000);
                stw.Stop();
                Console.WriteLine("執行緒ID:{0},執行完成,執行結果:{1},執行用時{2},", Thread.CurrentThread.ManagedThreadId, result, stw.ElapsedMilliseconds);
            };


            ThreadPool.QueueUserWorkItem(wc1);
            ThreadPool.QueueUserWorkItem(wc2);
            ThreadPool.QueueUserWorkItem(wc3);

            Console.ReadKey();
        }

        static long SumNumbers(int count)
        {
            long sum = 0;
            for (int i = 0; i < count; i++)
            {
                sum += i;
            }
            Thread.Sleep(1000);
            return sum;
        }
    }

 //等待執行緒池的執行緒執行
class Program
    {
        static void Main(string[] args)
        {
            using (ManualResetEvent m1 = new ManualResetEvent(false))
            using (ManualResetEvent m2 = new ManualResetEvent(false))
            using (ManualResetEvent m3 = new ManualResetEvent(false))
            {
                ThreadPool.QueueUserWorkItem(
                    s =>{
                        Console.WriteLine("執行緒ID:{0},開始執行", Thread.CurrentThread.ManagedThreadId);
                        Stopwatch stw = new Stopwatch();
                        stw.Start();
                        long result = SumNumbers(10000000);
                        stw.Stop();
                        m1.Set();
                        Console.WriteLine("執行緒ID:{0},執行完成,執行結果:{1},執行用時{2},", Thread.CurrentThread.ManagedThreadId, result, stw.ElapsedMilliseconds);
                    });

                ThreadPool.QueueUserWorkItem(
                    s =>{
                        Console.WriteLine("執行緒ID:{0},開始執行", Thread.CurrentThread.ManagedThreadId);
                        Stopwatch stw = new Stopwatch();
                        stw.Start();
                        long result = SumNumbers(10000000);
                        stw.Stop();
                        m2.Set();
                        Console.WriteLine("執行緒ID:{0},執行完成,執行結果:{1},執行用時{2},", Thread.CurrentThread.ManagedThreadId, result, stw.ElapsedMilliseconds);
                    });
                ThreadPool.QueueUserWorkItem(
                    s =>{
                        Console.WriteLine("執行緒ID:{0},開始執行", Thread.CurrentThread.ManagedThreadId);
                        Stopwatch stw = new Stopwatch();
                        stw.Start();
                        long result = SumNumbers(10000000);
                        stw.Stop();
                        m3.Set();
                        Console.WriteLine("執行緒ID:{0},執行完成,執行結果:{1},執行用時{2},", Thread.CurrentThread.ManagedThreadId, result, stw.ElapsedMilliseconds);
                    });

                //等待執行緒池的執行緒執行
                m1.WaitOne();
                m2.WaitOne();
                m3.WaitOne();
                Console.WriteLine("所有執行緒執行完成");

            }

            Console.ReadKey();
        }

        static long SumNumbers(int count)
        {
            long sum = 0;
            for (int i = 0; i < count; i++)
            {
                sum += i;
            }
            Thread.Sleep(3000);
            return sum;
        }
    }