Preventing a multithreaded website from having too many resources
I have built a mass mailing website to send to a client who has to send 80,000 emails in one message. It basically creates a new thread to send so that control can be pushed back to the UI (so the feedback page can load) and then a new thread is created for each company to send emails to their recipients. All emails are sent to the queue 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 sending 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);
This all works great and can send 40,000 newsletters in 10 minutes. The only problem is that the RAM and CPU on the server are consumed 100% in those 10 minutes. This affects other sites on the server as they cannot be accessed.
Is there a way to restrict the sending application's resource usage either in IIS 7.5 or by modifying the code above?
source to share
Problems:
-
You spawn a thread inside Parallel ForEach, the "Parallel" part means it already spawns a thread for the body. You are embedding Parallel Invoke instead of Action inside Parallel ForEach inside another Action.
-
You run a while loop inside a thread with no rest for the CPU. This is a 5x Parallel Call.
Answers:
To use the CPU, you need to give the recycling a breather. While in a TryDequeue loop, take a short nap.
MailMessage message;
while (queue.TryDequeue(out message))
{
SendMessage(sendingServer, message, factory);
Thread.Sleep(16);
}
For RAM and CPU usage, you need to process LESS immediately.
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);
source to share