Подтвердить что ты не робот

Правильный способ асинхронной отправки электронной почты в 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... 

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

4b9b3361

Ответ 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);
    }
} 

Ответ 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