首頁 > 軟體

C#實現自定義執行緒池範例程式碼

2022-07-18 22:05:23

在專案中如果是web請求時候,IIS會自動分配一個執行緒來進行處理,如果很多個應用程式共用公用一個IIS的時候,執行緒分配可能會出現一個問題(當然也是我的需求造成的)

之前在做專案的時候,有一個需求,就是當程式啟動的時候,希望能夠啟動一定數目的執行緒,然後每一個執行緒始終都是在執行的狀態,不進行釋放,然後迴圈去做一些事情。那麼IIS的執行緒管理可能就不是我想要的,因為我想我的一些程式,只用我開啟的執行緒來做工作。也就是說我想模擬一個執行緒池,每次有一個呼叫的時候從自定義執行緒池中取出一個,用完再放回去。

談談我的思路:

1.程式一啟動就通過for迴圈來建立,一定數目的執行緒(這個數目是可以設定的)

2.至少要有三個容器來儲存執行緒,分別是工作執行緒佇列和空閒執行緒佇列以及等待佇列

3.使用執行緒中的AutoResetEvent類,初始每一個執行緒都是unsignaled狀態,執行緒一啟動就一直在迴圈呼叫WaitOne()方法,那麼每次外部呼叫的時候,都呼叫一次這個類範例物件的set,執行緒然後就可以繼續做下面的工作了。

4.至少兩個方法:

第一個開放給外部,讓外部的方法能夠被傳入執行,然後這個方法能夠判斷空閒佇列,等待佇列,以及工作佇列的狀態,如果傳入的時候發現,空閒佇列有空閒的執行緒就直接,將任務委託給空閒佇列的一個執行緒執行,否則把它放到等待佇列。

第二個方法,需要能夠將工作完成的執行緒從工作佇列移動到空閒佇列,然後判斷一下等待佇列是不是有任務,有的話就交給空閒佇列裡面的執行緒來執行。

體思路如上,可以試試先寫一下。

1.因為每個執行緒都有一個AutoResetEvent的範例,所以最好把Thread進行封裝,變成我們自己的Thread。

    public class Task
    {
        #region Variable
        //一個AutoResetEvent範例
        private AutoResetEvent _locks = new AutoResetEvent(false);
        //一個Thread範例
        private Thread _thread;
        // 繫結回撥方法,就是外部實際執行的任務
        public Action _taskAction;

        //定義一個事件用來繫結工作完成後的操作,也就是4中所說的工作佇列向空閒佇列移動
        public event Action<Task> WorkComplete;

        /// <summary>
        ///設定執行緒擁有的Key
        /// </summary>
        public string Key { get; set; }

        #endregion

        //執行緒需要做的工作
        private void Work()
        {
            while (true)
            {
                //判斷訊號狀態,如果有set那麼 _locks.WaitOne()後的程式就繼續執行
                _locks.WaitOne();
                _taskAction();
                //執行事件
                WorkComplete(this);
            }
        }

        #region event
        //建構函式
        public Task()
        {
            //スレッドオブジェクトを初期化する
            _thread = new Thread(Work);
            _thread.IsBackground = true;
            Key = Guid.NewGuid().ToString();
            //執行緒開始執行
            _thread.Start();
        }

        //Set開起訊號
        public void Active()
        {
            _locks.Set();
        }

        #endregion
    }

解釋:上面那個Key的作用,因為多個執行緒同時進行的時候,我們並不知道哪一個執行緒的工作先執行完,所以說上面的工作佇列,實際上應該用一個字典來儲存,這樣我們就能在一個執行緒結束工作之後,通過這 裡的KEY(每個執行緒不一樣),來進行定位了。

2.執行緒封裝好了,然後就可以實現執行緒池了

    public class TaskPool
    {
        #region Variable
        //建立的執行緒數
        private int _threadCount;
        //空閒執行緒佇列
        private Queue<Task> _freeQueue;
        //工作執行緒字典(為什麼?)
        private Dictionary<string, Task> _workingDictionary;
        //空閒佇列,存放需要被執行的外部函數
        private Queue<Action> _waitQueue;
        #endregion

        #region Event
        //自定義執行緒池的建構函式
        public TaskPool()
        {
            _workingDictionary = new Dictionary<string, Task>();
            _freeQueue = new Queue<Task>();
            _waitQueue = new Queue<Action>();
            _threadCount = 10;

            Task task = null;
            //產生固定數目的執行緒
            for (int i = 0; i < _threadCount; i++)
            {
                task = new Task();
                //給每一個任務繫結事件
                task.WorkComplete += new Action<Task>(WorkComplete);
                //將每一個新建立的執行緒放入空閒佇列中
                _freeQueue.Enqueue(task);
            }
        }

        //執行緒任務完成之後的工作
        void WorkComplete(Task obj)
        {
            lock (this)
            {
                //將執行緒從字典中排除
                _workingDictionary.Remove(obj.Key);
                //將該執行緒放入空閒佇列
                _freeQueue.Enqueue(obj);

                //判斷是否等待佇列中有任務未完成
                if (_waitQueue.Count > 0)
                {
                    //取出一個任務
                    Action item = _waitQueue.Dequeue();
                    Task newTask = null;
                    //空閒佇列中取出一個執行緒
                    newTask = _freeQueue.Dequeue();
                    // 執行緒執行任務
                    newTask._taskAction = item;
                    //把執行緒放入到工作佇列當中
                    _workingDictionary.Add(newTask.Key, newTask);
                    //設定號誌
                    newTask.Active();
                    return;
                }
                else
                {
                    return;
                }
            }
        }

        //新增任務到執行緒池
        public void AddTaskItem(Action taskItem)
        {
            lock (this)
            {
                Task task = null;
                //判斷空閒佇列是否存線上程
                if (_freeQueue.Count > 0)
                {
                    //存線上程,取出一個執行緒
                    task = _freeQueue.Dequeue();
                    //將該執行緒放入工作佇列
                    _workingDictionary.Add(task.Key, task);
                    //執行傳入的任務
                    task._taskAction = taskItem;
                    //設定號誌
                    task.Active();
                    return;
                }
                else
                {
                    //空閒佇列中沒有空閒執行緒,就把任務放到等待佇列中
                    _waitQueue.Enqueue(taskItem);
                    return;
                }
            }
        }
        #endregion
    }

解釋:這裡的兩個方法,基本符合我的設想,注意每一個方法裡面都有lock操作,這就保證了,多個執行緒進行操作相同的佇列物件的時候,能夠進行互斥。保證一個時間只有一個執行緒在操作。

測試程式碼:

    class Program
    {
        static void Main(string[] args)
        {
            TaskPool _taskPool = new TaskPool();

            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
            for (var i = 0; i < 20; i++)
            {
                _taskPool.AddTaskItem(Print);
            }
            Console.Read();
        }

        public static void Print()
        {
            Console.WriteLine("Do Something!");
        }
    }

這裡我執行了20次print操作,看看結果是啥:

從圖中看到20次確實執行了,但是看不到執行緒是哪些,稍微修改一下自定義的執行緒池。

1.在自定義執行緒的建構函式中新增:如下程式碼,檢視哪些執行緒被建立了

        public Task()
        {
            _thread = new Thread(Work);
            _thread.IsBackground = true;
            Key = Guid.NewGuid().ToString();
            //執行緒開始執行
            _thread.Start();
            Console.WriteLine("Thread:"+_thread.ManagedThreadId+" has been created!");
        }

2.線上程完成工作方法之後新增如下程式碼,檢視哪些執行緒參與執行任務

        private void Work()
        {
            while (true)
            {
                //判斷訊號狀態,如果有set那麼 _locks.WaitOne()後的程式就繼續執行
                _locks.WaitOne();
                _taskAction();
                Console.WriteLine("Thread:" + Thread.CurrentThread.ManagedThreadId+"workComplete");
                //執行事件
                WorkComplete(this);
            }
        }

3.修改使用者端程式

    class Program
    {
        static void Main(string[] args)
        {
            TaskPool _taskPool = new TaskPool();

            for (var i = 0; i < 20; i++)
            {
                _taskPool.AddTaskItem(Print);
            }
            Console.Read();
        }

        public static void Print()
        {
            Thread.Sleep(10000);
        }
    }

測試結果:

從結果可以看到,開始和執行的執行緒都是固定的那10個,所以這個程式是可用的。

到此這篇關於C#自定義執行緒池的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com