William Hurst
William Hurst

Reputation: 2239

Prevent multi-threaded website from consuming too many resources

I have built a bulk email sending website for a client that is required to send out 80,000 emails in a single send. It basically creates a new thread for the send so that control can be handed back to the UI (so that a feedback page can load) and then a new thread is created for each company in order to send emails to their recipients. The emails are all queued up using this code:

// 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);

While the emails are queued, the send engine takes over and pulls emails from the queue and then sends them using the new Parallel class:

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);

All of this works great and can send 40,000 newsletters out in about 10 minutes. The only problem is that the RAM and CPU on the server are 100% consumed for those 10 minutes. This affects other sites on the server as they can't be accessed.

Is there any way to restrict the resource usage of the sending application either in IIS 7.5 or by changing the code above?

Upvotes: 3

Views: 273

Answers (1)

Louis Ricci
Louis Ricci

Reputation: 21116

Problems:

  • You are generating a thread inside of a Parallel ForEach, The "Parallel" part means it's already spawning a thread for the body. You are nesting Parallel Invoke instead of an Action inside of a Parallel ForEach inside of another Action.

  • You are running a while loop inside of a thread with no rest for the CPU. That is being Parallel Invoked 5x.

Answers:

For CPU usage, you need to give your processing a breather. In your While TryDequeue loop put a short Sleep.

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

For RAM and CPU usage, you need to process LESS at once.

        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);

Upvotes: 2

Related Questions