Reputation: 2239
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
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