文章出處

接上文 多線程編程學習筆記——線程池(一)

 

三、線程池與并行度

此示例是學習如何應用線程池實現大量的操作,及與創建大量線程進行工作的區別。

1. 代碼如下

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading; 

namespace ThreadPoolDemo
{  

    class Program
    {   

        static void Main(string[] args)
        {

            Console.WriteLine("開始測試線程池與自創線程。。。");
            const int total = 500;
            long millisecondes = 0;
            Stopwatch sw = new Stopwatch();
            sw.Start();
            ThreadRun(total);
            sw.Stop();
            millisecondes = sw.ElapsedMilliseconds;
            decimal mom = (decimal)(Environment.WorkingSet / (1024 * 1024.0));            sw.Reset();

            sw.Start();
            ThreadPoolRun(total);

            sw.Stop();
            Console.WriteLine("自創線程總耗時 {0},占用內存:{1} MB", millisecondes,mom);
            Console.WriteLine("線程池總耗時 {0} ,占用內存:{1} MB", sw.ElapsedMilliseconds, Environment.WorkingSet / (1024 * 1024.0));
            Console.Read();

        }

        private static void  ThreadRun(int total)
        {
            using (var countdown = new CountdownEvent(total))
            {
                Console.WriteLine("開始--自創線程。。。");
                for (int i = 0; i < total; i++)
                {
                    var t = new Thread(() =>
                    {
                        Console.WriteLine("自創線程ID :{0}", Thread.CurrentThread.ManagedThreadId);
                        Thread.Sleep(TimeSpan.FromSeconds(0.1));
                        countdown.Signal();//向 CountdownEvent 注冊信號,同時減小 CurrentCount 的值。

                    });
                    t.Start();
                }
                countdown.Wait();  // 阻塞當前線程,直到 CountdownEvent 的信號數量變為 0
                Console.WriteLine("-----------------");
            }
        }

        private static void ThreadPoolRun(int total)
        {
            using (var countdown = new CountdownEvent(total))

            {
                Console.WriteLine("開始--線程池。。。");
                for (int i = 0; i < total; i++)
                {
                    ThreadPool.QueueUserWorkItem(_ =>
                    {
                        Console.WriteLine("線程池工作線程ID :{0}", Thread.CurrentThread.ManagedThreadId);
                        Thread.Sleep(TimeSpan.FromSeconds(0.1));
                        countdown.Signal();//向 CountdownEvent 注冊信號,同時減小 CurrentCount 的值。
                    });                 

                }
                countdown.Wait();  // 阻塞當前線程,直到 CountdownEvent 的信號數量變為 0
                Console.WriteLine("-----------------");

            }
        }
    }
}

 

2.程序運行結果如下圖。

 

    1) 這個示例中我們自己創建了500個線程,每個線程一個操作,每個線程都阻塞100毫秒。總計耗時  11秒,消耗資源如下圖。

 

    2)我們使用線程池執行相同的500個操作。總計耗時  9秒,消耗資源如下圖。

 

從1)與2)的比較上可以看出來,自創線程消耗的CPU資源比線程池要多。

 

四、 從線程池中取消操作

如果我們要從線程池中取消某個線程的操作,應該如何實現呢?本示例使用CancellationTokenSource和CancellationToken兩個類來實現在取消線程池中的操作。這兩個是是在net 4.0引入的。

 1.示例代碼

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading; 

namespace ThreadPoolDemo
{

    class Program
    {    

        static void Main(string[] args)
        {
            Console.WriteLine("開始測試線程池中取消正在運行的線程。。。");
            using (var cts = new CancellationTokenSource())
            {
                CancellationToken token = cts.Token;
                ThreadPool.QueueUserWorkItem(_ => AsyncOper(token));
                Thread.Sleep(TimeSpan.FromSeconds(2));
                cts.Cancel();//取消線程操作
            }

            using (var cts = new CancellationTokenSource())
            {
                CancellationToken token = cts.Token;
                ThreadPool.QueueUserWorkItem(_ => AsyncOperation(token));
                Thread.Sleep(TimeSpan.FromSeconds(2));
                cts.Cancel();//取消線程操作

            }
            using (var cts = new CancellationTokenSource())

            {
                CancellationToken token = cts.Token;
                ThreadPool.QueueUserWorkItem(_ => AsyncOper3(token));

                Thread.Sleep(TimeSpan.FromSeconds(2));
                cts.Cancel();//取消線程操作
            }

            Thread.Sleep(TimeSpan.FromSeconds(2));
            Console.WriteLine("。。。。。。。。。。取消正在運行的線程結束。。。。。。");
            Console.Read();
        }

        private static void  AsyncOperation(CancellationToken token)
        {

            try
            {
                Console.WriteLine("開始--線程池中的第二個工作線程。。。");
                for (int i = 0; i < 5; i++)

                {
                    token.ThrowIfCancellationRequested();//獲取取消請求,拋出OperationCanceledException異常,
                    Thread.Sleep(TimeSpan.FromSeconds(1));
                }

                Console.WriteLine("-------線程池中的第二個工作線程 工作完成----------");
            }
            catch (OperationCanceledException ex)
            {
                Console.WriteLine("使用拋出異常方法取消第二個工作線程  ID:{0},{1}", Thread.CurrentThread.ManagedThreadId,ex.Message);
            }
        }
        private static void AsyncOper(CancellationToken token)
        {

            Console.WriteLine("開始--線程池中的第一個工作線程。。。");
            for (int i = 0; i < 5; i++)
            {
                if (token.IsCancellationRequested)//判斷是否已經取消操作
                {
                    Console.WriteLine("使用輪詢方法取消工作線程  ID:{0}", Thread.CurrentThread.ManagedThreadId);
                    return;
                }
                Thread.Sleep(TimeSpan.FromSeconds(1));
            }
            Console.WriteLine("-------線程池中的第一個工作線程 工作完成----------");
        }
        private static void AsyncOper3(CancellationToken token)
        {
            Console.WriteLine("開始--線程池中的第三個工作線程。。。");
            bool cancel = false;
            token.Register(()=>cancel = true);
            for (int i = 0; i < 5; i++)
            {
                if (cancel)//判斷是否已經取消操作
                {
                    Console.WriteLine("通過注冊回調函數取消第三個工作線程  ID:{0}", Thread.CurrentThread.ManagedThreadId);
                    return;
                }
                Thread.Sleep(TimeSpan.FromSeconds(1));
            }   

            Console.WriteLine("-------線程池中的第三個工作線程 工作完成----------");
        }
    }
}

2.運行結果如下圖。

 

本示例一共實現了三種取消線程池中操作的方式。

  1. 輪詢檢查CancellationToken.IsCancellationRequested屬性,如果為true,則說明操作被取消。
  2. 拋出一個OperationCancellationException異常。這允許操作之外的代碼來取消操作。
  3. 注冊一個回調函數,當操作取消時,線程池將調用回調函數,這樣做的好處是將取消操作邏輯傳遞到另一個異步操作中。

文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()