ThreadPool.QueueUserWorkItem的性能问题

摘要:
更好的方法是使用线程队列。对于线程队列ThreadPool许多人应该熟悉QueueUserWorkItem。以下是Microsoft的解释:将方法排队执行,并指定包含该方法使用的数据的对象。它的功能是将一些操作放入当前线程以外的线程中执行。它的用法是simple://Code2线程池QueueUserWorkItem;与代码1相比,它具有使用已创建的空闲线程的优势。如果没有空闲线程,它将排队,而不是一直盲目创建。

在WEB开发中,为了减少页面等待时间提高用户体验,我们往往会把一些浪费时间的操作放到新线程中在后台运行。

简单的实现代码就是:

//代码一  
new Thread(()=>{  
//do something  
}).Start();  

  但是对于一个请求量大的网址这样做是很不现实的——每一个操作都要开启一个新线程,最终会因CPU不堪重负而使网站挂掉。

更好的做法是使用线程队列。

对于线程队列 ThreadPool.QueueUserWorkItem 很多人应该都不陌生,下边看微软的解释:

将方法排入队列以便执行,并指定包含该方法所用数据的对象。此方法在有线程池线程变得可用时执行。

它的作用就是将一些操作放入当前线程之外的另外一个线程中执行,它的使用方法很简单:

//代码二  
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));
                }
            }

        }  

  该类的使用很简单:

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导致很多任务还没有被执行就被丢弃。

无论怎么,这种设计还是适用于很多“一般情况”。

  

免责声明:文章转载自《ThreadPool.QueueUserWorkItem的性能问题》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇关于向SQL Server导入Excel出错的统一解决方法AspxGridView使用手记下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Golang(三)Goroutine原理

前言 最近用到了一些 Golang 异步编程的地方,感觉 Golang 相对于其他语言(如 Java)对多线程编程的支持非常大,使用起来也非常方便。于是决定了解一下 Goroutine 的底层原理。 Goroutine 本质是协程,是实现并行计算的核心。只需要在对应的函数前加上 Go 关键词即可异步执行: go func() { }() 基本概念...

斗鱼直播三面:说说JDK与JRE的区别是什么!

前言 JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。 JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚...

python安装 hanlp +使用 坑坑坑。。。填填填。。。

刚开始用都不知道要怎么安装,包括什么文件。百度了下发现正常安装就可以用,不用下多余的东西,但是但是但是但是但是但是但是但是 用pycharm安装不行,pip安装不行,也是见鬼了。 解决方法: 1、手动安装JPype1 pip install D:softJPype1-0.6.2-cp36-cp36m-win_amd64.whl 这个安装包可以在这里下 ...

操作系统: 用户级线程和内核级线程

http://www.cnblogs.com/yxzfscg/p/4758728.html 三种线程——内核线程、轻量级进程、用户线程 内核线程 内核线程就是内核的分身,一个分身可以处理一件特定事情。这在处理异步事件如异步IO时特别有用。内核线程的使用是廉价的,唯一使用的资源就是内核栈和上下文切换时保存寄存器的空间。支持多线程的内核叫做多线程内核(Mult...

使用数据字典layui搜索框三联动

/** * 自定义的搜索初始化插件 * 说明: * 给需要异步加载数据字典的加上自定义属性jq-search="qu" * qu为数据字典对应的key * 如果需要级联查询的则加上自定义属性lay-filter, * 如果没有下一级或者子集则不需要加 * 三联动第一个lay-filter设成1,第三个设成3,代码中有判定 * echo用于放...

多核时代,还在使用任务管理器来看程序的性能吗?

是否为了一个程序只占用了13%的CPU利用率而沾沾自喜呢? 别忘了现在是多核时代,四核,超线程的i7. 单个线程最多只能占到12.5%的CPU利用率。 忘记这个简单的CPU利用率吧,多核时代,你需要具体知道到每个线程的利用率。 这篇博客,来源一个刚刚遇到的问题。 背景是我们正在针对Windows Media Player开发一些东西,具体是什么不重要。...