luketurkey
New Member
I'm having a problem getting this chunk of code to work (sorry for the length). The code is sterilized to just show the relevant portions in diagnosing the problem.The purpose of the code is to interactively communicate with a web control, and let it know when the emails have been sent. Previously, there was no multi-threading at all and the process took AGES to send tens of thousands of emails.It works fine when run from a console app, but when run from an ASP.NET page, it fails. The emails send fine without tying up the UI, but we don't get confirmation that the emails were sent, so that the web app can report this to the user.In an ASP.NET scenario, the BulkEmailCompleted routine never fires, and it is this routine that increments the m_CompletedWorkers counter.The end result of this is that the While loop in the SendBulkEmail routine is never ending because the m_CompletedWorkers counter never goes above zero.I'm struggling to understand why this works in a console, but fails when called from the web. ... I also need to know how to fix it. I'm not a threading expert, I didn't write this code, and I'm confused.I think the basis of this code was gotten from this website:http://www.dotnetfunda.com/articles/article613-background-processes-in-asp-net-web-applications.aspxAny help will be appreciated.The code follows:IN THE WEBSITE PROJECT (Control.ascx.cs)\[code\]using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.UI;using System.Web.UI.WebControls;using System.Web.UI.HtmlControls;using System.Configuration;using <company>.BusinessObjects.Emails;using <company>.DataAccessors;using <company>.DataObjects;using <company>.Enumerations;using <company>.Interfaces;using <company>.Utilities; ... protected void sendButton_OnClick(object sender, EventArgs e) { ... if (HasBenefits) { ReportingEmails emailer = new ReportingEmails(); ... //Prevent send if nothing selected if (... > 0) { List<EmailOutcome> results = emailer.GenerateNotificationEmailsForEmployer(<some int>, <some list>); ... } } ... }\[/code\]IN THE BUSINESS OBJECT PROJECT\[code\]using System;using System.Collections.Generic;using System.Configuration;using System.Linq;using System.Net.Mail;using System.Resources;using System.Text;using System.IO;using <company>.BusinessObjects.Emails.Helpers;using <company>.DataAccessors;using <company>.DataObjects;using <company>.Enumerations;using <company>.Utilities;namespace <company>.BusinessObjects.Emails{ public class ReportingEmails { ... public List<EmailOutcome> GenerateNotificationEmailsForEmployer(int employerID, List<int> benefits = null) { ... SendNotificationEmails(List<ReportData>, ref emailSuccessByBenefit, true, benefitsToExclude); return emailSuccessByBenefit; } private void SendNotificationEmails(List<ReporterCommsData> reportData, ref List<EmailOutcome> emailSuccessByBenefit, bool isFleet, List<int> benefitsToExclude) { Dictionary<int, MailMessage> bulkEmails = new Dictionary<int, MailMessage>(); //build up the set of emails to send foreach (ReporterCommsData report in reportData) { ... if (String.IsNullOrEmpty(report.Email)) { ... } else { try { MailMessage email = null; ... email = ConstructEmail(<param>, out <param>, <param>); ... bulkEmails.Add(report.BenefitID, email); //add each email to the bulk email dictionary ... } catch (Exception ex) { ... } } } //end foreach //do the bulk email send and get the outcomes try { ... emailSuccessByBenefit.AddRange(Utilities.Mail.SendBulkEmail(bulkEmails, credentials)); } catch (Exception ex) { ... } } ... } ...}\[/code\]IN THE UTILITIES PROJECT\[code\]using System;using System.Collections.Generic;using System.ComponentModel;using System.Linq;using System.Text;using System.Net.Mail;using System.Threading;namespace <company>.Utilities{ ... public class Mail { private static List<EmailOutcome> m_MultithreadedEmailSendResults = new List<EmailOutcome>(); private static int m_CompletedWorkers = 0; ... /// <summary> /// Sends a large number of emails asynchronously and then reports success of the individual items collectively /// </summary> /// <param name="emails">A dictionary of completed MailMessage objects to send out, keyed on an ID</param> /// <param name="credentials">Network credentials which may be required to send the email</param> /// <returns>List of EmailOutcome objects signifying the success or failure of sending each individual email</returns> public static List<EmailOutcome> SendBulkEmail(Dictionary<int, MailMessage> emails, System.Net.NetworkCredential credentials = null) { const int NUMBER_OF_THREADS_PER_PROCESSOR = 1; m_CompletedWorkers = 0; List<EmailOutcome> results = new List<EmailOutcome>(); List<Dictionary<int, MailMessage>> splitEmailList = new List<Dictionary<int, MailMessage>>(); ... List<BackgroundWorker> workerThreads = new List<BackgroundWorker>(); foreach (Dictionary<int, MailMessage> splitEmails in splitEmailList) { // Initialise the parameter array Object[] parameterArray = new Object[2]; parameterArray[0] = splitEmails; parameterArray[1] = credentials; // Runs on function startup BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += new DoWorkEventHandler(BulkEmailWorker); worker.WorkerReportsProgress = false; worker.WorkerSupportsCancellation = true; worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BulkEmailCompleted); //Add worker to collection workerThreads.Add(worker); //Calling the BulkEmailWorker asynchronously worker.RunWorkerAsync(parameterArray); } //Hold until all background workers complete while (workerThreads.Count > m_CompletedWorkers) { Thread.Sleep(500); //Wait a half second } //Dispose of BackgroundWorkers foreach (BackgroundWorker worker in workerThreads) { worker.Dispose(); } //Get results results.AddRange(m_MultithreadedEmailSendResults); //Clear the static variable m_MultithreadedEmailSendResults.Clear(); m_MultithreadedEmailSendResults = new List<EmailOutcome>(); return results; } ... /// <summary> /// Event handler for the RunWorkerCompleted event. Adds the EmailOutcome results to the static /// </summary> private static void BulkEmailCompleted(object sender, RunWorkerCompletedEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; //Add the EmailOutcome objects to the static list of results if completed if (worker != null) { Thread.Sleep(200); worker.RunWorkerAsync(); } else { m_MultithreadedEmailSendResults.AddRange(e.Result as List<EmailOutcome>); m_CompletedWorkers++; } } ... } ...}\[/code\]