代码之家  ›  专栏  ›  技术社区  ›  ProfK

ASP.NET页请求中的多线程库调用

  •  2
  • ProfK  · 技术社区  · 15 年前

    我有一个ASP.NET应用程序,非常基础,但如果幸运的话,现在有太多的代码要发布,我不需要。

    我们有一个叫 ReportGenerator . 单击按钮,调用方法GenerateReports。它对 InternalGenerateReports 使用 ThreadPool.QueueUserWorkItem 并返回,结束ASP.NET响应。它不提供任何完成回调或任何内容。

    内部GenerateReports 在threadpool中创建和维护五个线程,每个线程一个报告,也可以使用 QueueUserWorkItem 通过“创建”五个线程,也可以在一个循环中使用并等待所有线程的调用完成。每个线程都使用ASP.NET ReportViewer控件将报表呈现为HTML。也就是说,对于200份报告, 内部GenerateReports 应创建5个线程40次。当线程完成时,报告数据将排队,当所有五个线程都完成时,报告数据将刷新到磁盘。

    我最大的问题是,在运行了一个报告之后,aspnet进程被挂起,而且在大约200个报告中,应用程序被挂起。

    我只是将这段代码简化为在一个线程中运行,这样做很好。在我们讨论像我的代码这样的细节之前,上面的scendario中有什么明显的可能是错误的吗?

    下面是代码的一个简化示例:

    public class SscceReports
    {
        Dictionary<Guid, AsyncReportCreator> runningWorkers = new Dictionary<Guid, AsyncReportCreator>();
        public void GenerateReports(Queue<int> reportKeys)
        {
            int goodPoolSize = System.Environment.ProcessorCount;
            System.Threading.ThreadPool.SetMaxThreads(goodPoolSize + 10, goodPoolSize * 10);
            System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(InternalGenerateReports), reportKeys);
        }
    
        void InternalGenerateReports(object state)
        {
            Queue<int> reportKeys = state as Queue<int>;
            while (reportKeys.Count > 0)
            {
                for (int i = 0; i < 5 && reportKeys.Count > 0; i++)
                {
                    Guid workerId = Guid.NewGuid();
                    int rk = (int) reportKeys.Dequeue();
                    AsyncReportCreator asrc = new AsyncReportCreator(rk);
                    runningWorkers.Add(workerId, asrc);
                    asrc.WorkComplete += CompleteCallBack;
                    System.Threading.ThreadPool.QueueUserWorkItem(asrc.StartWork);
                }
                while (runningWorkers.Count > 0)
                    System.Threading.Thread.Sleep(500);
            }
            while (runningWorkers.Count > 0)
                System.Threading.Thread.Sleep(5000);
        }
    
        void CompleteCallBack(object state)
        {
            // Write queued report content to disk.
            runningWorkers.Remove((Guid) state);
        }
    }
    
    public class AsyncReportCreator
    {
        public event System.Threading.WaitCallback WorkComplete;
        private int key;
        public AsyncReportCreator(int reportKey)
        {
            key = reportKey;
        }
    
        public void StartWork(object state)
        {
            // Create report;
            WorkComplete(state);
        }
    }
    
    2 回复  |  直到 15 年前
        1
  •  0
  •   Kiril    15 年前

    .NET ThreadPool 默认情况下 25 threads per processor ,所以如果您在调用时生成200个报告 InternalGenerateReports 它将在 线程池 (200份报告*5个工作项目 内部GenerateReports )一次只能激活25个,但您可以看到,此模型可能不适合您。

    线程池的默认限制为 每个可用处理器25个线程, 可以使用 corsetmaxthreads定义见 mscoree.h文件。每个线程使用 默认堆栈大小并在 默认优先级。每个过程可以 只有一个操作系统线程 池。

    考虑使用生产者/消费者模式,而不是将25个工作项(即25个线程)排队,只需创建几个消费者线程(与您拥有的处理器/核心数量匹配),这些线程处理从生产者填充的中央阻塞队列生成报告的请求。这会使事情变得更合理…

    some articles 也就是说你不应该使用 线程池 与ASP.NET:

    你可以用螺丝刀 在ASP.NET和IT中完全相同 工作正如你所期望的。这个 问题不在线程池中 本身,但在ASP.NET使用的其他方面 同时。ASP.NET是 多线程设计和使用 服务页面和 映射到ASP.NET ISAPI的内容 过滤器。

    如果您还使用了threadpool,那么 ASP.NET使用的线程更少 请求被搁置,直到 池返回一个空闲线程。这可能 对于低交通量来说不是问题 网站,但更受欢迎的网站可以 陷入困境。低流量站点可以 如果他们使用 线程工具很多。

    更新

    我想我发现了你的问题…你在排队 asrc.StartWork 但你不会通过 state 所以什么时候 CompleteCallBack 被称为它没有任何要从 runningWorkers . 以下是为您改进的版本:

    public class CountDownLatch {
        private volatile int m_remain;
        private EventWaitHandle m_event;
    
        public CountDownLatch(int count) {
            m_remain = count;
            m_event = new ManualResetEvent(false);
        }
    
        public void Signal() {
            // The last thread to signal also sets the event.
            if (Interlocked.Decrement(ref m_remain) == 0)
                m_event.Set();
        }
    
        public void Wait() {
            m_event.WaitOne();
        }
    }
    
    public class SscceReports
    {
        public void GenerateReports(Queue<int> reportKeys)
        {
            int goodPoolSize = System.Environment.ProcessorCount;
            System.Threading.ThreadPool.SetMaxThreads(goodPoolSize + 10, goodPoolSize * 10);
            System.Threading.ThreadPool.QueueUserWorkItem(o=>
            {
                InternalGenerateReports(reportKeys);
            });
        }
    
        void InternalGenerateReports(Queue<int> reportKeys)
        {
            // assuming that the reportKeys.Count contains only 5 keys,
            // we create a countdown latch to hande the same number of keys
            CountDownLatch latch = new CountDownLatch(reportKeys.Count);
            foreach( int rk in reportKeys)
            {
                AsyncReportCreator asrc = new AsyncReportCreator(rk);
                asrc.WorkComplete += CompleteCallBack;
                System.Threading.ThreadPool.QueueUserWorkItem(o=>
                {
                    asrc.StartWork(latch);
                });
            }
    
            // Wait for all the tasks to complete instead of sleeping
            latch.Wait(); // <- blocks untill all (5) tasks call latch.Signal()
        }
    
        void CompleteCallBack(CountDownLatch latch)
        {
            // Write queued report content to disk.
            latch.Signal(); 
        }
    }
    
    public class AsyncReportCreator
    {
        public delegate void WorkComplete(CountDownLatch latch);
        public AsyncReportCreator(int reportKey)
        {
            key = reportKey;
        }
    
        public void StartWork(CountDownLatch latch)
        {
            // Create report;
            WorkComplete(latch);
        }
    }
    
        2
  •  0
  •   Amethi    15 年前

    如果您正在使用或可以使用框架4.0,那么新的并行扩展非常好,并且对于并行处理非常简单。我将它与BackgroundWorker类结合起来创建了非常强大但简单的处理引擎:

    http://msdn.microsoft.com/en-us/library/dd460720.aspx

    比自己处理所有线程容易得多。这个比例非常大。