在项目中如果是web请求时候,IIS会自动分配一个线程来进行处理,如果很多个应用程序共享公用一个IIS的时候,线程分配可能会出现一个问题(当然也是我的需求造成的)
之前在做项目的时候,有一个需求,就是当程序启动的时候,希望能够启动一定数目的线程,然后每一个线程始终都是在运行的状态,不进行释放,然后循环去做一些事情。那么IIS的线程管理可能就不是我想要的,因为我想我的一些程序,只用我开启的线程来做工作。也就是说我想模拟一个线程池,每次有一个调用的时候从自定义线程池中取出一个,用完再放回去。
谈谈我的思路:
1.程序一启动就通过for循环来创建,一定数目的线程(这个数目是可以配置的)
2.至少要有三个容器来存储线程,分别是工作线程队列和空闲线程队列以及等待队列
3.使用线程中的AutoResetEvent类,初始每一个线程都是unsignaled状态,线程一启动就一直在循环调用WaitOne()方法,那么每次外部调用的时候,都调用一次这个类实例对象的set,线程然后就可以继续做下面的工作了。
4.至少两个方法:
第一个开放给外部,让外部的方法能够被传入执行,然后这个方法能够判断空闲队列,等待队列,以及工作队列的状态,如果传入的时候发现,空闲队列有空闲的线程就直接,将任务委托给空闲队列的一个线程执行,否则把它放到等待队列。
第二个方法,需要能够将工作完成的线程从工作队列移动到空闲队列,然后判断一下等待队列是不是有任务,有的话就交给空闲队列里面的线程来执行。
体思路如上,可以试试先写一下。
1.因为每个线程都有一个AutoResetEvent的实例,所以最好把Thread进行封装,变成我们自己的Thread。
1 public class Task 2 { 3 #region Variable 4 //一个AutoResetEvent实例 5 private AutoResetEvent _locks = new AutoResetEvent(false); 6 //一个Thread实例 7 private Thread _thread; 8 // 绑定回调方法,就是外部实际执行的任务 9 public Action _taskAction; 10 11 //定义一个事件用来绑定工作完成后的操作,也就是4中所说的工作队列向空闲队列移动 12 public event Action<Task> WorkComplete; 13 14 /// <summary> 15 ///设置线程拥有的Key 16 /// </summary> 17 public string Key { get; set; } 18 19 #endregion 20 21 //线程需要做的工作 22 private void Work() 23 { 24 while (true) 25 { 26 //判断信号状态,如果有set那么 _locks.WaitOne()后的程序就继续执行 27 _locks.WaitOne(); 28 _taskAction(); 29 //执行事件 30 WorkComplete(this); 31 } 32 } 33 34 #region event 35 //构造函数 36 public Task() 37 { 38 //スレッドオブジェクトを初期化する 39 _thread = new Thread(Work); 40 _thread.IsBackground = true; 41 Key = Guid.NewGuid().ToString(); 42 //线程开始执行 43 _thread.Start(); 44 } 45 46 //Set开起信号 47 public void Active() 48 { 49 _locks.Set(); 50 } 51 52 #endregion 53 }
解释:上面那个Key的作用,因为多个线程同时进行的时候,我们并不知道哪一个线程的工作先执行完,所以说上面的工作队列,实际上应该用一个字典来保存,这样我们就能在一个线程结束工作之后,通过这 里的KEY(每个线程不一样),来进行定位了。
2.线程封装好了,然后就可以实现线程池了
1 public class TaskPool 2 { 3 4 #region Variable 5 //创建的线程数 6 private int _threadCount; 7 //空闲线程队列 8 private Queue<Task> _freeQueue; 9 //工作线程字典(为什么?) 10 private Dictionary<string, Task> _workingDictionary; 11 //空闲队列,存放需要被执行的外部函数 12 private Queue<Action> _waitQueue; 13 #endregion 14 15 #region Event 16 //自定义线程池的构造函数 17 public TaskPool() 18 { 19 _workingDictionary = new Dictionary<string, Task>(); 20 _freeQueue = new Queue<Task>(); 21 _waitQueue = new Queue<Action>(); 22 _threadCount = 10; 23 24 Task task = null; 25 //产生固定数目的线程 26 for (int i = 0; i < _threadCount; i++) 27 { 28 task = new Task(); 29 //给每一个任务绑定事件 30 task.WorkComplete += new Action<Task>(WorkComplete); 31 //将每一个新创建的线程放入空闲队列中 32 _freeQueue.Enqueue(task); 33 } 34 } 35 36 //线程任务完成之后的工作 37 void WorkComplete(Task obj) 38 { 39 lock (this) 40 { 41 //将线程从字典中排除 42 _workingDictionary.Remove(obj.Key); 43 //将该线程放入空闲队列 44 _freeQueue.Enqueue(obj); 45 46 //判断是否等待队列中有任务未完成 47 if (_waitQueue.Count > 0) 48 { 49 //取出一个任务 50 Action item = _waitQueue.Dequeue(); 51 Task newTask = null; 52 //空闲队列中取出一个线程 53 newTask = _freeQueue.Dequeue(); 54 // 线程执行任务 55 newTask._taskAction = item; 56 //把线程放入到工作队列当中 57 _workingDictionary.Add(newTask.Key, newTask); 58 //设置信号量 59 newTask.Active(); 60 return; 61 } 62 else 63 { 64 return; 65 } 66 } 67 } 68 69 //添加任务到线程池 70 public void AddTaskItem(Action taskItem) 71 { 72 lock (this) 73 { 74 Task task = null; 75 //判断空闲队列是否存在线程 76 if (_freeQueue.Count > 0) 77 { 78 //存在线程,取出一个线程 79 task = _freeQueue.Dequeue(); 80 //将该线程放入工作队列 81 _workingDictionary.Add(task.Key, task); 82 //执行传入的任务 83 task._taskAction = taskItem; 84 //设置信号量 85 task.Active(); 86 return; 87 } 88 else 89 { 90 //空闲队列中没有空闲线程,就把任务放到等待队列中 91 _waitQueue.Enqueue(taskItem); 92 return; 93 } 94 } 95 } 96 #endregion 97 98 }
解释:这里的两个方法,基本符合我的设想,注意每一个方法里面都有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.在自定义线程的构造函数中添加:如下代码,查看哪些线程被创建了
1 public Task() 2 { 3 _thread = new Thread(Work); 4 _thread.IsBackground = true; 5 Key = Guid.NewGuid().ToString(); 6 //线程开始执行 7 _thread.Start(); 8 Console.WriteLine("Thread:"+_thread.ManagedThreadId+" has been created!"); 9 }
2.在线程完成工作方法之后添加如下代码,查看哪些线程参与执行任务
1 private void Work() 2 { 3 while (true) 4 { 5 //判断信号状态,如果有set那么 _locks.WaitOne()后的程序就继续执行 6 _locks.WaitOne(); 7 _taskAction(); 8 Console.WriteLine("Thread:" + Thread.CurrentThread.ManagedThreadId+"workComplete"); 9 //执行事件 10 WorkComplete(this); 11 } 12 }
3.修改客户端程序
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 TaskPool _taskPool = new TaskPool(); 6 7 for (var i = 0; i < 20; i++) 8 { 9 _taskPool.AddTaskItem(Print); 10 } 11 Console.Read(); 12 } 13 14 public static void Print() 15 { 16 Thread.Sleep(10000); 17 } 18 19 }
测试结果:
从结果可以看到,开始和执行的线程都是固定的那10个,所以这个程序是可用的。