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

时间:2010-06-04 22:40:15

标签: .net asp.net multithreading

我有一个非常基本的ASP.NET应用程序,但是如果我们很幸运而且我不需要,那么现在有太多代码可以发布。

我们有一个名为ReportGenerator的班级。单击按钮,将调用方法GenerateReports。它使用InternalGenerateReportsThreadPool.QueueUserWorkItem进行异步调用并返回,结束ASP.NET响应。它不提供任何完成回调或任何东西。

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

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

我只是将此代码简化为在单个线程中运行,这样可以正常工作。在我们进入像我的代码之类的细节之前,在上面的版本中有什么明显的可能是错误的吗?

以下是代码的简要示例:

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 个答案:

答案 0 :(得分:0)

.NET ThreadPool默认为25 threads per processor,因此如果您在致电InternalGenerateReports时生成200份报告,则会在ThreadPool中排队1,000个工作项( 200个报告*每个InternalGenerateReports 5个工作项。一次只能激活25个,但您可以看到此模型可能不适合您。

  

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

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

some articles表示不应在ASP.NET中使用ThreadPool

  

您可以使用ThreadPool   在ASP.NET和它中完全相同的方式   像你期望的那样工作。该   问题不在ThreadPool中   本身,但在ASP.NET使用的其他东西   这是为了同时。 ASP.NET是   多线程的设计和使用   ThreadPool用于提供页面和   映射到ASP.NET ISAPI的内容   过滤

     

如果你也使用ThreadPool,那么   ASP.NET使用的线程较少   和请求被搁置,直到   pool返回一个免费线程。这有可能   对于低流量而言,这不是问题   网站,但更受欢迎的网站可以得到   陷入麻烦。低流量站点可以   如果他们使用的话会遇到麻烦   ThreadPool很多。

更新

我认为我发现了你的问题......你正在排队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);
    }
}

答案 1 :(得分:0)

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

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

比自己处理所有线程容易得多。这可以很好地扩展。