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

17

Когда пользователь регистрируется на моем веб-сайте, я не понимаю, почему мне нужно заставить его «ждать», чтобы 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... 

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

    
задан Ralph N 05.01.2012 в 19:09
источник

9 ответов

21

Было много хороших советов, которые я поддержал здесь ... например, чтобы не забыть использовать 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.")));
    }
}
    
ответ дан Ralph N 06.01.2012 в 13:50
источник
5

Начиная с .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);
    }
} 
    
ответ дан Boris Lipschitz 18.03.2014 в 07:02
источник
3

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

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

    
ответ дан asawyer 05.01.2012 в 19:14
источник
3

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

Итак, вместо mailer.SendTheMail () попробуйте следующее:

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

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

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

    
ответ дан Richard 05.01.2012 в 19:26
источник
3

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

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

Что лучше практики использования SmtpClient, SendAsync и Dispose в .NET 4.0

    
ответ дан hatchet 05.01.2012 в 19:22
источник
2

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

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

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

  

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

Также стоит прочитать статью Википедии о CQRS ( Ссылка ). Важным моментом, который здесь подчеркивается, является:

  

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

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

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

    
ответ дан A. Murray 11.03.2014 в 23:32
источник
1

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

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

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

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

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

    
ответ дан asdf_enel_hak 05.01.2012 в 19:29
источник
1

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

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 ";
        }
    
ответ дан Rahul 18.05.2013 в 12:02
источник
0

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

Ссылка

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

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

Удачи!

Matt

    
ответ дан Matt Cashatt 05.01.2012 в 19:13
источник