Получение многопоточности для синхронизации между веб-страницей и бизнес-уровнем
У меня проблема с получением этого куска кода для работы (извините за длину). Код стерилизован, чтобы просто показать соответствующие части в диагностике проблемы.
Он отлично работает при запуске из консольного приложения. Но когда Utlities.SendBulkEmail вызывается из приложения ASP.NET, процедура BulkEmailCompleted никогда не срабатывает, и именно эта процедура увеличивает счетчик m_CompletedWorkers.
Как мне провести рефакторинг подпрограммы SendBulkEmail для использования AsyncOperationManager вместо BackgroundWorker, чтобы я мог гарантировать поток, в котором возвращаются результаты.
Сама подпрограмма SendBulkEmail не является многопоточной. Многопоточность происходит внутри цикла foreach.
Я думаю, что основа исходного кода была получена с этого сайта: http://www.dotnetfunda.com/articles/article613-background-processes-in-asp-net-web-applications.aspx
Проект Utilities используется разными решениями и в значительной степени изолирован.
Я надеюсь, что я проясняю это.
Любая помощь будет оценена.
Код следует:
В ПРОЕКТЕ САЙТА (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>);
...
}
}
...
}
В ПРОЕКТЕ БИЗНЕС-ОБЪЕКТА
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)
{
...
}
}
...
}
...
}
В КОММУНАЛЬНОМ ПРОЕКТЕ
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++;
}
}
...
}
...
}
1 ответ
Asp.net работает так, что он создает объект Page для каждого запроса. То же самое касается контроля. Ваши уведомления об отправляемых письмах могут достигать только этот объект.
Созданные вами потоки будут выполняться не сразу, и ваш ответ не будет ждать окончания отправки письма. Это означает, что вы не можете отправить статус, используя тот же запрос.
Однако, если вы сделаете другой запрос, ajax или иным образом со страницы, чтобы получить обновленный статус, будет создана новая страница и соответствующие объекты управления. Вам нужно будет получить статус от статических объектов и использовать его для отображения статуса пользователю.
Вы можете найти элемент управления UpdatePanel удобным для реализации ajax.