防止多线程网站消耗过多资源

3

我为客户建立了一个群发电子邮件的网站,需要一次性发送80,000封电子邮件。它基本上为发送创建了一个新的线程,以便可以将控制权交回UI(以便可以加载反馈页面),然后为每个公司创建一个新的线程,以便向其收件人发送电子邮件。使用以下代码将所有电子邮件排队等待:

// Loop through the companies and send their mail to the specified recipients
        // while creating a new thread for each company
        // A new thread is started so that the feedback page can load
        SendingThread = Task.Factory.StartNew(() =>
        {
            // This is a thread safe for loop
            Parallel.ForEach<CompanyEntity>(companies, company =>
            {
                    // Start a new thread for each company send
                    Task.Factory.StartNew(() =>
                    {
                        // Get the recipients for this company
                        var companyRecipients = GetSubscribersForCompany(company.Id, recipients);

                        // Send the newsletter to the company recipients
                        var success = SendNewsletterForCompany(newsletter, company, companyRecipients, language,
                                                               version, company.AdvertCollectionViaNewsletterCompanyAdvertLink, newsletter.NewsletterType, email);

                        // Add the status update so the front end can view a list of updated conpany statuses
                        if (success)
                            AddStatusUpdate(company.CompanyTitle + " has completed processing.");

                        // Starts sending the emails if the engine hasn't already been started
                        SendEngine.Start(CurrentSmtpClient, this);

                    }).ContinueWith(antecendent => EndCompaniesSendUpdate(companiesToProcess, companiesProcessed), TaskContinuationOptions.OnlyOnRanToCompletion);
            });
        }, new CancellationToken(), TaskCreationOptions.LongRunning, TaskScheduler.Default);

当电子邮件被排队时,发送引擎会接管并从队列中拉取电子邮件,然后使用新的Parallel类发送它们:

Action action = () =>
        {
            MailMessage message;
            while (queue.TryDequeue(out message))
            {
                SendMessage(sendingServer, message, factory);
            }
        };

        // Start 5 concurrent actions to send the messages in parallel.
        Parallel.Invoke(action, action, action, action, action);

所有这些功能都很好,可以在大约10分钟内发送40,000封新闻通讯。唯一的问题是服务器上的RAM和CPU在这10分钟内被100%消耗。这会影响其他站点,因为它们无法访问。

有没有办法限制发送应用程序的资源使用,无论是在IIS 7.5中还是通过更改上面的代码?


1
在网站中在线程中初始化长时间运行的任务是不正确的。这就是Windows服务的用途。 - Stilgar
1
我不知道你的队列通常是空的还是已经存在元素,但是如果队列为空,一个 while(TryDequeue) 循环将会吸取核心的生命力。你使用轮询而非在该队列上使用适当的信号有什么原因吗? - Martin James
@Stilgar 很遗憾,我没有预算来重新开发它作为一个Windows服务,但我意识到我应该从一开始就考虑这种方式。 - William Hurst
@MartinJames 我不知道信号选项,我会再做些研究。 - William Hurst
1个回答

2

问题:

  • 在Parallel.ForEach中生成一个线程,"Parallel"部分表示已经为body生成了一个线程。你正在将Parallel.Invoke嵌套在另一个Action的Parallel.ForEach内。

  • 您正在一个线程内运行没有休息CPU的while循环。这被Parallel Invoked 5次。

答案:

为了减少CPU使用率,您需要给处理过程喘息的时间。在While TryDequeue循环中放置一个短的Sleep即可。

        MailMessage message;
        while (queue.TryDequeue(out message))
        {
            SendMessage(sendingServer, message, factory);
            Thread.Sleep(16);
        }

对于RAM和CPU的使用,您需要一次处理较少的内容。

        SendingThread = Task.Factory.StartNew(() =>
        {
            foreach(var company in companies) 
            {
                        // Get the recipients for this company
                        var companyRecipients = GetSubscribersForCompany(company.Id, recipients);

                        // Send the newsletter to the company recipients
                        var success = SendNewsletterForCompany(newsletter, company, companyRecipients, language,
                                                               version, company.AdvertCollectionViaNewsletterCompanyAdvertLink, newsletter.NewsletterType, email);

                        // Add the status update so the front end can view a list of updated conpany statuses
                        if (success)
                            AddStatusUpdate(company.CompanyTitle + " has completed processing.");

                        // Starts sending the emails if the engine hasn't already been started
                        SendEngine.Start(CurrentSmtpClient, this);


            }
       }, new CancellationToken(), TaskCreationOptions.LongRunning, TaskScheduler.Default);

啊哈,我以为 Parallel.ForEach 只是一个线程安全的循环。我没有意识到它会启动自己的线程。我已经重新修改了代码,并添加了 Thread.Sleep,这将资源使用降至可接受的水平。谢谢。 - William Hurst

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接