Правильный способ асинхронной отправки электронной почты в ASP.NET... (я делаю это правильно?)
Когда пользователь регистрируется на моем веб-сайте, я не понимаю, почему мне нужно заставить его "ждать", чтобы smtp прошел, чтобы он получил адрес электронной почты активации.
Я решил, что хочу запустить этот код асинхронно, и это было приключение.
Предположим, что у меня есть метод, например:
private void SendTheMail() { // Stuff }
Мой первый, хотя.. был потоковым. Я сделал это:
Emailer mailer = new Emailer();
Thread emailThread = new Thread(() => mailer.SendTheMail());
emailThread.Start();
Это работает... пока я не решил проверить его на возможности обработки ошибок. Я намеренно сломал адрес сервера SMTP в своем web.config и попробовал его. Страшный результат заключался в том, что IIS в основном BARFED с необработанной ошибкой исключения на w3wp.exe(это была ошибка Windows! Насколько экстремальна...) ELMAH (мой регистратор ошибок) НЕ поймал ее И IIS был перезапущен, поэтому любой, кто на сайте их сеанс стирается. Полностью неприемлемый результат!
Моя следующая мысль заключалась в том, чтобы провести некоторое исследование асинхронных делегатов. Кажется, что это работает лучше, потому что исключения обрабатываются внутри делегата asynch (в отличие от примера потока выше). Тем не менее, я обеспокоен, если я делаю это неправильно или, возможно, я вызываю утечку памяти.
Вот что я делаю:
Emailer mailer = new Emailer();
AsyncMethodCaller caller = new AsyncMethodCaller(mailer.SendMailInSeperateThread);
caller.BeginInvoke(message, email.EmailId, null, null);
// Never EndInvoke...
Я делаю это правильно?
Ответы
Ответ 1
Было много хороших советов, которые я поддержал здесь... например, чтобы не забыть использовать IDisposable (я полностью не знал). Я также понял, насколько важно вручную ловить ошибки, когда в другом потоке, так как нет контекста - я работал над теорией, что я должен просто позволить ELMAH обрабатывать все. Кроме того, дальнейшие исследования заставили меня понять, что я тоже забыл использовать IDisposable в mailmessage.
В ответ на Ричарда, хотя я вижу, что решение для потоковой обработки может работать (как было предложено в моем первом примере) до тех пор, пока я поймаю ошибки... есть еще что-то страшное в том, что IIS полностью взрывается, если это ошибка не поймана. Это говорит мне, что ASP.NET/IIS никогда не предназначался для вас, чтобы это сделать... вот почему я склоняюсь к тому, чтобы продолжать использовать .BeginInvoke/delegates вместо этого, поскольку это не испортит IIS, когда что-то пойдет не так и кажется более популярны в ASP.NET.
В ответ на ASawyer я был полностью удивлен тем, что в SMTP-клиент был встроен .SendAsync. Некоторое время я играл с этим решением, но, похоже, это не трюк для меня. Хотя я могу пропустить клиент кода, который делает SendAsync, страница все еще "ждет" до тех пор, пока не будет выполнено событие SendCompleted. Моя цель состояла в том, чтобы пользователь и страница перемещались вперед, когда электронное письмо отправляется в фоновом режиме. У меня такое чувство, что я все еще могу делать что-то неправильно... так что, если кто-то приходит к этому, они могут попробовать сами.
Здесь мое полное решение для того, как я отправил электронные письма на 100% асинхронно в дополнение к регистрации ошибок ELMAH.MVC. Я решил перейти с расширенной версией примера 2:
public void SendThat(MailMessage message)
{
AsyncMethodCaller caller = new AsyncMethodCaller(SendMailInSeperateThread);
AsyncCallback callbackHandler = new AsyncCallback(AsyncCallback);
caller.BeginInvoke(message, callbackHandler, null);
}
private delegate void AsyncMethodCaller(MailMessage message);
private void SendMailInSeperateThread(MailMessage message)
{
try
{
SmtpClient client = new SmtpClient();
client.Timeout = 20000; // 20 second timeout... why more?
client.Send(message);
client.Dispose();
message.Dispose();
// If you have a flag checking to see if an email was sent, set it here
// Pass more parameters in the delegate if you need to...
}
catch (Exception e)
{
// This is very necessary to catch errors since we are in
// a different context & thread
Elmah.ErrorLog.GetDefault(null).Log(new Error(e));
}
}
private void AsyncCallback(IAsyncResult ar)
{
try
{
AsyncResult result = (AsyncResult)ar;
AsyncMethodCaller caller = (AsyncMethodCaller)result.AsyncDelegate;
caller.EndInvoke(ar);
}
catch (Exception e)
{
Elmah.ErrorLog.GetDefault(null).Log(new Error(e));
Elmah.ErrorLog.GetDefault(null).Log(new Error(new Exception("Emailer - This hacky asynccallback thing is puking, serves you right.")));
}
}
Ответ 2
Начиная с .NET 4.5 SmtpClient реализует асинхронный ожидаемый метод
SendMailAsync
.
В результате для асинхронного посылки электронной почты необходимо:
public async Task SendEmail(string toEmailAddress, string emailSubject, string emailMessage)
{
var message = new MailMessage();
message.To.Add(toEmailAddress);
message.Subject = emailSubject;
message.Body = emailMessage;
using (var smtpClient = new SmtpClient())
{
await smtpClient.SendMailAsync(message);
}
}
Ответ 3
Используете ли вы .Net SmtpClient для отправки электронной почты? Он может отправлять асинхронные сообщения уже.
Изменить - если Emailer mailer = new Emailer();
не является оболочкой над SmtpClient, это не будет так полезно, я думаю.
Ответ 4
Если вы используете классы .Net SmtpClient и MailMessage, вы должны принять к сведению пару вещей. Во-первых, ожидайте ошибки на отправке, поэтому ловушки и обработайте их. Во-вторых, в .Net 4 были внесены некоторые изменения в эти классы, и оба теперь реализуют IDisposable (MailMessage с 3.5, SmtpClient new в 4.0). Из-за этого ваше создание SmtpClient и MailMessage должно быть завернуто с использованием блоков или явно удалено. Некоторые люди не знают об этом.
См. этот вопрос SO для получения дополнительной информации об утилизации при использовании асинхронных сообщений:
Каковы наилучшие методы использования SmtpClient, SendAsync и Dispose в .NET 4.0
Ответ 5
Threading - это неправильный вариант здесь, но если вы не будете обрабатывать исключение самостоятельно, оно будет пузыриться и разбивать ваш процесс. Неважно, какой поток вы делаете.
Итак, вместо mailer.SendTheMail() попробуйте следующее:
new Thread(() => {
try
{
mailer.SendTheMail();
}
catch(Exception ex)
{
// Do something with the exception
}
});
Еще лучше, используйте асинхронные возможности SmtpClient, если сможете. Однако вам все равно придется обрабатывать исключения.
Я бы даже предложил вам взглянуть на .Net 4 новую библиотеку задач Parallet. Это имеет дополнительные функции, которые позволяют обрабатывать исключительные случаи и хорошо работать с пулом потоков ASP.Net.
Ответ 6
Итак, почему бы не иметь отдельного poller/service, который занимается исключительно отправкой писем? Таким образом, позволяя выполнять регистрацию после обратной записи только за время, которое требуется для записи в очередь базы данных/сообщений, и откладывание отправки электронной почты до следующего интервала опроса.
Я сейчас обдумываю ту же проблему, и я думаю, что я действительно не хочу даже инициировать отправку электронной почты в запросе на отправку по серверу. Процесс обслуживания веб-страниц должен быть заинтересован в получении ответа от пользователя ASAP, тем больше работы вы пытаетесь сделать медленнее, чем будет.
Посмотрите на Принцип разделения запросов команд (http://martinfowler.com/bliki/CQRS.html). Мартин Фаулер объясняет, что в командной части операции могут использоваться разные модели, чем в части запроса. В этом случае команда будет "регистрировать пользователя", запрос будет электронной почтой активации, используя свободную аналогию. Соответствующая цитата, вероятно, будет:
В отдельных моделях мы чаще всего подразумеваем разные объектные модели, вероятно, работающие в разных логических процессах
Также стоит прочитать статью Википедии о CQRS (http://en.wikipedia.org/wiki/Command%E2%80%93query_separation). Важным моментом, который здесь подчеркивается, является:
он явно предназначен как руководство по программированию, а не правило для хорошего кодирования
Значение, используйте его там, где ваш код, выполнение программы и понимание программиста выиграют. Это хороший пример сценария.
Этот подход имеет дополнительное преимущество, чтобы отрицать все проблемы, связанные с муфтием, и головные боли, которые могут принести.
Ответ 7
Я работал над тем же вопросом для моего проекта:
Сначала попробовал Thread
, как вы:
- Я потерял контекст
- Проблема обработки исключений
- Обычно сказано, Thread
- плохая идея в IIS ThreadPool
Итак, я переключаюсь и стараюсь с помощью asynchronously
:
- "асинхронно" - это fake
в веб-приложении asp.net. Он просто помещает вызовы в очередь и swicth контекст
Итак, я делаю службу Windows и извлекаю значения через таблицу sql: happy end
Итак, для быстрого решения: от ajax
side сделать асинхронный вызов сообщить пользователю fake
да, но продолжить задание отправки в вашем контроллере mvc
Ответ 8
Используйте этот способ -
private void email(object parameters)
{
Array arrayParameters = new object[2];
arrayParameters = (Array)parameters;
string Email = (string)arrayParameters.GetValue(0);
string subjectEmail = (string)arrayParameters.GetValue(1);
if (Email != "[email protected]")
{
OnlineSearch OnlineResult = new OnlineSearch();
try
{
StringBuilder str = new StringBuilder();
MailMessage mailMessage = new MailMessage();
//here we set the address
mailMessage.From = fromAddress;
mailMessage.To.Add(Email);//here you can add multiple emailid
mailMessage.Subject = "";
//here we set add bcc address
//mailMessage.Bcc.Add(new MailAddress("[email protected]"));
str.Append("<html>");
str.Append("<body>");
str.Append("<table width=720 border=0 align=left cellpadding=0 cellspacing=5>");
str.Append("</table>");
str.Append("</body>");
str.Append("</html>");
//To determine email body is html or not
mailMessage.IsBodyHtml = true;
mailMessage.Body = str.ToString();
//file attachment for this e-mail message.
Attachment attach = new Attachment();
mailMessage.Attachments.Add(attach);
mailClient.Send(mailMessage);
}
}
protected void btnEmail_Click(object sender, ImageClickEventArgs e)
{
try
{
string To = txtEmailTo.Text.Trim();
string[] parameters = new string[2];
parameters[0] = To;
parameters[1] = PropCase(ViewState["StockStatusSub"].ToString());
Thread SendingThreads = new Thread(email);
SendingThreads.Start(parameters);
lblEmail.Visible = true;
lblEmail.Text = "Email Send Successfully ";
}
Ответ 9
Если вы хотите обнаружить утечки, вам нужно использовать профайлер, подобный этому:
http://memprofiler.com/
Я не вижу в вашем решении ничего плохого, но вы можете почти гарантировать, что этот вопрос будет закрыт как субъективный.
Еще один вариант - использовать jQuery для вызова ajax на сервер и искрообразования потока электронной почты. Таким образом, пользовательский интерфейс не заблокирован.
Удачи!
Matt