ThreadPool.QueueUserWorkItem的性能问题
在WEB开发中,为了降低页面等待时间提高用户体验,我们往往会把一些浪费时间的操作放到新线程中在后台执行。
简单的实现代码就是:
//代码一 new Thread(()=>{ //do something }).Start();
可是对于一个请求量大的网址这样做是非常不现实的——每个操作都要开启一个新线程,终于会因CPU不堪重负而使站点挂掉。
更好的做法是使用线程队列。
对于线程队列 ThreadPool.QueueUserWorkItem 非常多人应该都不陌生,下边看微软的解释:
将方法排入队列以便运行,并指定包括该方法所用数据的对象。此方法在有线程池线程变得可用时运行。
它的作用就是将一些操作放入当前线程之外的另外一个线程中运行,它的用法非常easy:
//代码二 ThreadPool.QueueUserWorkItem(stat => { //do something }, null);
它相对代码一的长处是会利用已经创建过的空暇的线程,假设没有空暇就排队,而不会盲目的一直创建下去。
可是它并没有摆脱“创建新线程”的问题:过多的线程会占用很多其它的资源。由此我们不难想到,我们为什么不自己搞个队列,让它们在同一个线程中逐个运行?对此,我写了个简单的实现类:
public class BackgroundTasks { private class TaskEntity { public TaskEntity(Action<object> func, object data) { this.Function = func; this.Data = data; } public Action<object> Function; public object Data; } static Queue<TaskEntity> list = new Queue<TaskEntity>(); static BackgroundTasks() { Thread th = new Thread(RunTask); th.IsBackground = true; th.Start(); } static void RunTask() { while (true) { if (list.Count==0) { Thread.Sleep(1000); } else { TaskEntity entity; lock (list) { entity = list.Dequeue(); } try { entity.Function(entity.Data); } catch { } Thread.Sleep(10); } } } public static void Add(Action<object> func, object data) { lock (list) { list.Enqueue(new TaskEntity(func, data)); } } }
该类的使用非常easy:
BackgroundTasks.Add((obj)=>{
Console.WriteLine("这个任务的加入时间是:{0}", obj as DateTime);
}, DateTime.Now);
另一个“实例版”的,就是针对每一个方法,分别创建一个任务队列:
public class BackgroundTasks<T> { private Action<T> Function; private Queue<T> list = new Queue<T>(); public BackgroundTasks(Action<T> func) { this.Function = func; Thread th = new Thread(RunTask); th.IsBackground = true; th.Start(); } private void RunTask() { while (true) { if (list.Count == 0) { Thread.Sleep(1000); } else { T data; lock (list) { data = list.Dequeue(); } try { Function(data); } catch { } Thread.Sleep(10); } } } public void Add(T data) { lock (list) { list.Enqueue(data); } } }
调用演示样例:
var bg = new BackgroundTasks<Blog>((blog) => { Console.WriteLine(blog.BlogId); }); int i = 0; while (i++ < 1000) { bg.Add(new Blog() { BlogId = i }); }
这个设计既攻克了异步运行,又攻克了占用资源的问题。
可是世界上没有完美的东西,代码也是如此,因为队列中的任务是单线程运行,可能会导致某些任务在非常长时间后才会被运行到,或者重新启动IIS导致非常多任务还没有被运行就被丢弃。
不管怎么,这样的设计还是适用于非常多“普通情况”。