Правильный способ асинхронной отправки электронной почты в ASP.NET ... (я делаю это правильно?) - PullRequest
19 голосов
/ 05 января 2012

Когда пользователь регистрируется на моем веб-сайте, я не понимаю, почему мне нужно заставить его «ждать», пока не пройдет smtp, чтобы он получил электронное письмо для активации.

Я решил, что хочу запустить этот код асинхронно, и это было приключение.

Давайте представим, что у меня есть метод, такой как:

private void SendTheMail() { // Stuff }

Мое первое, хотя .. было нить. Я сделал это:

Emailer mailer = new Emailer();
Thread emailThread = new Thread(() => mailer.SendTheMail());
emailThread.Start();

Это работает ... пока я не решил проверить его на возможность обработки ошибок. Я намеренно сломал адрес SMTP-сервера в моем файле web.config и попробовал его. Страшным результатом было то, что IIS в основном СДЕЛАНО с ошибкой необработанного исключения на w3wp.exe (это была ошибка windows! Как экстремально ...) ELMAH (мой регистратор ошибок) НЕ перехватил его, и IIS был перезапущен, так что любой на сайте имел их сессия стерта. Совершенно неприемлемый результат!

Моей следующей мыслью было провести исследование асинхронных делегатов. Кажется, это работает лучше, потому что исключения обрабатываются в асинхронном делегате (в отличие от приведенного выше примера потока). Тем не менее, я обеспокоен, если я делаю это неправильно или, возможно, я вызываю утечки памяти.

Вот что я делаю:

Emailer mailer = new Emailer();
AsyncMethodCaller caller = new AsyncMethodCaller(mailer.SendMailInSeperateThread);
caller.BeginInvoke(message, email.EmailId, null, null);
// Never EndInvoke... 

Я правильно делаю?

Ответы [ 9 ]

25 голосов
/ 06 января 2012

Было много полезных советов, за которые я проголосовал здесь ... например, обязательно не забывайте использовать IDisposable (я совершенно не знал). Я также понял, как важно вручную отлавливать ошибки, когда в другом потоке нет контекста - я работал над теорией, согласно которой я должен просто позволить ELMAH обрабатывать все. Кроме того, дальнейшие исследования позволили мне понять, что я забыл использовать IDisposable и в почтовом сообщении.

В ответ Ричарду, хотя я вижу, что многопоточное решение может работать (как предложено в моем первом примере), пока я ловлю ошибки ... есть еще кое-что пугающее в том факте, что IIS полностью взрывается, если это ошибка не обнаружена Это говорит мне о том, что ASP.NET/IIS никогда не предназначался для вас, чтобы сделать это ... именно поэтому я склоняюсь к тому, чтобы продолжать использовать .BeginInvoke / делегаты, так как это не портит 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.")));
    }
}
6 голосов
/ 18 марта 2014

Начиная с .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 голосов
/ 05 января 2012

Потоковая обработка здесь не является неправильной опцией, но если вы сами не обработаете исключение, это приведет к сбою и падению вашего процесса.Не имеет значения, в каком потоке вы это делаете.

Так что вместо mailer.SendTheMail () попробуйте это:

new Thread(() => { 
  try 
  {
    mailer.SendTheMail();
  }
  catch(Exception ex)
  {
    // Do something with the exception
  }
});

Еще лучше, используйте асинхронные возможности SmtpClient, если выМожно.Вам все равно придется обрабатывать исключения.

Я бы даже посоветовал вам взглянуть на новую библиотеку Parallet Task .Net 4.Это имеет дополнительную функциональность, которая позволяет обрабатывать исключительные случаи и хорошо работает с пулом потоков ASP.Net.

3 голосов
/ 05 января 2012

Если вы используете классы .Net SmtpClient и MailMessage, вы должны принять к сведению несколько вещей.Во-первых, ожидайте ошибки при отправке, поэтому отлавливайте и обрабатывайте их.Во-вторых, в .Net 4 произошли некоторые изменения в этих классах, и оба теперь реализуют IDisposable (MailMessage начиная с 3.5, SmtpClient, новый в 4.0).Из-за этого ваше создание SmtpClient и MailMessage должно быть заключено в блоки или явно уничтожено.Это серьезное изменение, о котором некоторые люди не знают.

См. Этот вопрос SO для получения дополнительной информации об утилизации при использовании асинхронных отправок:

Каковы оптимальные методы использования SmtpClient, SendAsync иУтилизировать под .NET 4.0

3 голосов
/ 05 января 2012

Используете ли вы .Net SmtpClient для отправки электронной почты? Он может отправлять асинхронные сообщения уже .

Редактировать - если Emailer mailer = new Emailer(); не является оберткой над SmtpClient, это не будет настолько полезным, я думаю

2 голосов
/ 12 марта 2014

Итак, почему бы не иметь отдельный опросник / сервис, который занимается исключительно отправкой электронной почты?Таким образом, разрешив выполнение регистрации после возврата только за время, необходимое для записи в базу данных / очередь сообщений, и отложив отправку электронного письма до следующего интервала опроса.

Я размышляю над той же проблемойтолько сейчас, и я думаю, что я действительно не хочу даже инициировать отправку электронной почты на запрос обратной отправки на сервере.Процесс обслуживания веб-страниц должен быть заинтересован в получении ответа обратно пользователю как можно скорее, чем больше вы будете пытаться выполнить, тем медленнее это будет.

Посмотрите на принцип разделения запросов команд (http://martinfowler.com/bliki/CQRS.html). Мартин Фаулер объясняет, что в командной части операции могут использоваться модели, отличные от используемых в части запроса.В этом сценарии команда будет «регистрировать пользователя», запросом будет электронное письмо активации, используясвободная аналогия. Соответствующая цитата, вероятно, будет такой:

Под отдельными моделями мы обычно подразумеваем разные объектные модели, вероятно, работающие в разных логических процессах

Также стоит прочитатьстатья в Википедии о CQRS (http://en.wikipedia.org/wiki/Command%E2%80%93query_separation). Важный момент, на котором это подчеркивается:

, он явно задуман как руководство по программированию, а не как правило для хорошего кодирования

Значение, используйте его там, где ваш код, выполнение программы и понимание программистом будут полезныпоместиться.Это хороший пример сценария.

Этот подход имеет дополнительное преимущество, сводя на нет все проблемы с многопоточностью и головные боли, которые могут принести.

1 голос
/ 18 мая 2013

Используйте этот путь-

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@email.com")
        {
            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("bcc@site.com"));
                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 ";
        }
1 голос
/ 05 января 2012

Я работал над тем же вопросом для моего проекта:

Первый раз попробовал Thread как вы:
- Я теряю контекст
- Проблема обработки исключений
- Обычно говорят, Thread плохая идея в IIS ThreadPool

Поэтому я переключаюсь и пробую с asynchronously:
- «асинхронно» - это fake в веб-приложении asp.net. Он просто помещает вызовы в очередь и переключает контекст

Поэтому я создаю службу Windows и извлекаю значения через таблицу sql: happy end

Так что для быстрого решения: со стороны ajax сделайте асинхронный вызов, сообщите пользователю fake да, но продолжите отправку в вашем контроллере mvc

0 голосов
/ 05 января 2012

Если вы хотите обнаружить утечки, вам нужно использовать такой профилировщик, как этот:

http://memprofiler.com/

Я не вижу ничего неправильно с вашим решением, но могу почти гарантировать, что этот вопрос будет закрыт как субъективный.

Еще одним вариантом является использование jQuery для вызова ajax-сервера и запуска потока электронной почты. Таким образом, пользовательский интерфейс не заблокирован.

Удачи!

Мэтт

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...