0

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.

It works fine when run from a console app. But when Utlities.SendBulkEmail is called from an ASP.NET app, the BulkEmailCompleted routine never fires, and it is this routine that increments the m_CompletedWorkers counter.

How do I refactor the SendBulkEmail routine to use AsyncOperationManager instead of BackgroundWorker, so I can guarantee the thread that the results are returned on.

The SendBulkEmail routine itself is not multi-threaded. The multi-threading happens inside its foreach loop.

I think the basis of the original code was gotten from this website: http://www.dotnetfunda.com/articles/article613-background-processes-in-asp-net-web-applications.aspx

The Utilities project is shared among various solutions, and is pretty much stand alone.

I'm hoping that I'm making this clear.

Any help will be appreciated.

The code follows:

IN THE WEBSITE PROJECT (Control.ascx.cs)

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

IN THE BUSINESS OBJECT PROJECT

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)
            {
                ...
            }
        }
        ...
    }
    ...
}

IN THE UTILITIES PROJECT

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++;
            }
        }
        ...
    }
    ...
}
user1058946
  • 243
  • 2
  • 3
  • 14
  • Have you tried debugging it and following the steps of what it's doing? That would be your best bet. – Zipper Mar 20 '13 at 03:18
  • Yes, I've tried debugging. That's how I know that BulkEmailCompleted in the Utilities project isn't even being run. I think BulkEmailWorker is reporting back on a different thread or something, because the RunWorkerCompletedEventHandler doesn't seem to be firing when the job is complete. – user1058946 Mar 20 '13 at 05:39

1 Answers1

0

The way asp.net works is that it creates a Page object for each request. The same goes for controls. Your notifications of the emails being sent can only reach this object.

The threads you create will run take their time to execute, and your response will not wait for the email sending to complete. This means that you can't send the status using the same request.

However if you make another request, ajax or otherwise from the page, to get the updated status, a new Page and corresponding control objects will be created. You will have to get the status from you static objects and use that to show the status to a user.

You may find the UpdatePanel control handy to implement ajax.

nunespascal
  • 17,584
  • 2
  • 43
  • 46