• ThreadPool.QueueUserWorkItem的性能问题


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

    简单的实现代码就是:

    [csharp] view plaincopyprint?

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

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

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

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

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

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

    [csharp] view plaincopyprint?

    1. //代码二
    2. ThreadPool.QueueUserWorkItem(stat => {
    3. //do something
    4. }, null);
    //代码二
    ThreadPool.QueueUserWorkItem(stat => {
    //do something
    }, null);

    它相对代码一的优点是会利用已经创建过的空闲的线程,如果没有空闲就排队,而不会盲目的一直创建下去。

    但是它并没有摆脱“创建新线程”的问题:过多的线程会占用更多的资源。由此我们不难想到,我们为什么不自己搞个队列,让它们在同一个线程中逐个执行?对此,我写了个简单的实现类:

    [csharp] view plaincopyprint?

    1. public class BackgroundTasks
    2. {
    3. private class TaskEntity
    4. {
    5. public TaskEntity(Action<object> func, object data)
    6. {
    7. this.Function = func;
    8. this.Data = data;
    9. }
    10. public Action<object> Function;
    11. public object Data;
    12. }
    13. static Queue<TaskEntity> list = new Queue<TaskEntity>();
    14. static BackgroundTasks()
    15. {
    16. Thread th = new Thread(RunTask);
    17. th.IsBackground = true;
    18. th.Start();
    19. }
    20. static void RunTask()
    21. {
    22. while (true)
    23. {
    24. if (list.Count==0)
    25. {
    26. Thread.Sleep(1000);
    27. }
    28. else
    29. {
    30. TaskEntity entity;
    31. lock (list)
    32. {
    33. entity = list.Dequeue();
    34. }
    35. try
    36. {
    37. entity.Function(entity.Data);
    38. }
    39. catch { }
    40. Thread.Sleep(10);
    41. }
    42. }
    43. }
    44. public static void Add(Action<object> func, object data)
    45. {
    46. lock (list)
    47. {
    48. list.Enqueue(new TaskEntity(func, data));
    49. }
    50. }
    51. }
        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);

    还有一个“实例版”的,就是针对每个方法,分别创建一个任务队列:

    [csharp] view plaincopyprint?

    1. public class BackgroundTasks<T>
    2. {
    3. private Action<T> Function;
    4. private Queue<T> list = new Queue<T>();
    5. public BackgroundTasks(Action<T> func)
    6. {
    7. this.Function = func;
    8. Thread th = new Thread(RunTask);
    9. th.IsBackground = true;
    10. th.Start();
    11. }
    12. private void RunTask()
    13. {
    14. while (true)
    15. {
    16. if (list.Count == 0)
    17. {
    18. Thread.Sleep(1000);
    19. }
    20. else
    21. {
    22. T data;
    23. lock (list)
    24. {
    25. data = list.Dequeue();
    26. }
    27. try
    28. {
    29. Function(data);
    30. }
    31. catch { }
    32. Thread.Sleep(10);
    33. }
    34. }
    35. }
    36. public void Add(T data)
    37. {
    38. lock (list)
    39. {
    40. list.Enqueue(data);
    41. }
    42. }
    43. }
        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);
                }
            }
    
        }

    调用示例:

    [csharp] view plaincopyprint?

    1. var bg = new BackgroundTasks<Blog>((blog) => {
    2. Console.WriteLine(blog.BlogId);
    3. });
    4. int i = 0;
    5. while (i++ < 1000)
    6. {
    7. bg.Add(new Blog() { BlogId = i });
    8. }
    var bg = new BackgroundTasks<Blog>((blog) => { 
        Console.WriteLine(blog.BlogId); 
    });
    int i = 0;
    while (i++ < 1000)
    {
        bg.Add(new Blog() { BlogId = i });
    }

    这个设计既解决了异步执行,又解决了占用资源的问题。

    但是世界上没有完美的东西,代码也是如此,由于队列中的任务是单线程执行,可能会导致某些任务在很长时间后才会被执行到,或者重启IIS导致很多任务还没有被执行就被丢弃。

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

  • 相关阅读:
    Qt笔记之 信号和槽
    Qt笔记之 坐标系
    Qt笔记之 QPushButton
    点双连通
    bzoj 2179 FFT快速傅立叶
    bzoj 4128 矩阵求逆
    bzoj 3924 点分
    bzoj 1076 状态压缩最优期望
    bzoj 3143 随机游走
    bzoj 4084 双旋转字符串
  • 原文地址:https://www.cnblogs.com/sung/p/2888394.html
Copyright © 2020-2023  润新知