C#实现自定义线程池实例代码

 更新时间:2022年07月18日 16:36:30   作者:奋斗的大橙子  
这篇文章介绍了C#实现自定义线程池的实例代码,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

在项目中如果是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#自定义线程池的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • c#之事件用法

    c#之事件用法

    这篇文章介绍了c#中事件的用法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-04-04
  • C#实现Word转换TXT的方法详解

    C#实现Word转换TXT的方法详解

    这篇文章主要为大家详细介绍了如何利用C#实现Word转换TXT的功能,文中的示例代码讲解详细,对我们学习C#有一定的帮助,感兴趣的小伙伴可以跟随小编一起了解一下
    2022-12-12
  • .net 通过 WebAPI 调用nsfwjs 进行视频鉴别功能

    .net 通过 WebAPI 调用nsfwjs 进行视频鉴别功能

    这篇文章主要介绍了.net 通过 WebAPI 调用 nsfwjs 进行视频鉴别,文末给大家提到了FFMPEG获取视频关键帧并保存成jpg图像的相关知识,需要的朋友可以参考下
    2021-09-09
  • C# wpf定义ViewModelBase进行简化属性绑定

    C# wpf定义ViewModelBase进行简化属性绑定

    绑定机制是wpf的核心,也是界面独立的根本,尤其是使用了mvvm模式,本文主要介绍了wpf如何定义ViewModelBase进行简化属性绑定,需要的可以参考下
    2024-04-04
  • c# OpenCvSharp实现常见检测(斑点检测,轮廓检测,边缘检测)

    c# OpenCvSharp实现常见检测(斑点检测,轮廓检测,边缘检测)

    这篇文章主要为大家详细介绍了c#如何使用OpenCvSharp实现常见检测(斑点检测,轮廓检测,边缘检测),文中的示例代码讲解详细,需要的小伙伴可以参考下
    2023-12-12
  • 浅谈C# StringBuilder内存碎片对性能的影响

    浅谈C# StringBuilder内存碎片对性能的影响

    这篇文章主要介绍了浅谈StringBuilder内存碎片对性能的影响,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03
  • C# DatagridView常用操作汇总

    C# DatagridView常用操作汇总

    这篇文章主要介绍了C# DatagridView常用操作汇总,罗列了一些常用的用法与技巧,需要的朋友可以参考下
    2014-07-07
  • c#中如何获取指定字符前的字符串

    c#中如何获取指定字符前的字符串

    这篇文章主要介绍了c#中如何获取指定字符前的字符串问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • C#提取网页中超链接link和text部分的方法

    C#提取网页中超链接link和text部分的方法

    这篇文章主要介绍了C#提取网页中超链接link和text部分的方法,涉及C#正则表达式及字符串操作相关技巧,需要的朋友可以参考下
    2016-02-02
  • C#命令行参数解析库System.CommandLine使用

    C#命令行参数解析库System.CommandLine使用

    System.CommandLine是一个基于.Net Standard 2.0的命令行参数解析库,该项目还是属于beta状态,期待以后的正式版本,文章通过示例代码给大家介绍了System.CommandLine使用讲解,感兴趣的朋友一起看看吧
    2021-06-06

最新评论