Prawidłowy sposób asynchronicznie wysyłania wiadomości e-mail w ASP.NET ... (czy robię to dobrze?)

Kiedy użytkownik rejestruje się na mojej stronie, nie widzę powodu, dla którego muszę zmuszać go do "czekania" na przejście smtp, aby otrzymał e-mail aktywacyjny.

Zdecydowałem, że chcę uruchomić ten kod asynchronicznie, i to była przygoda.

Wyobraźmy sobie, że mam metodę, taką jak:

private void SendTheMail() { // Stuff }
Mój pierwszy.. był gwintowany. Zrobiłem to:
Emailer mailer = new Emailer();
Thread emailThread = new Thread(() => mailer.SendTheMail());
emailThread.Start();
To działa... dopóki nie postanowiłem przetestować go pod kątem możliwości obsługi błędów. Celowo złamałem adres serwera SMTP w moim www.config i próbowałem. Przerażającym rezultatem było to, że IIS w zasadzie wymiotował z nieobsługiwanym błędem wyjątku na w3wp.exe (to był błąd windows! jakie to ekstremalne...) ELMAH (mój rejestrator błędów) nie złapał go, a IIS został ponownie uruchomiony, więc każdy na stronie miał usuniętą sesję. Całkowicie niedopuszczalny wynik! Moją następną myślą było przeprowadzenie badań nad asynchronicznymi delegatami. Wydaje się to działać lepiej, ponieważ wyjątki są obsługiwane w delegacie asynch (w przeciwieństwie do wątku przykład powyżej). Jednak martwię się, czy robię to źle, czy może powoduję wycieki pamięci.

Oto co robię:

Emailer mailer = new Emailer();
AsyncMethodCaller caller = new AsyncMethodCaller(mailer.SendMailInSeperateThread);
caller.BeginInvoke(message, email.EmailId, null, null);
// Never EndInvoke... 
Czy dobrze to robię?
Author: Ralph N, 2012-01-05

9 answers

Było wiele dobrych rad, które podałem tutaj... na przykład pamiętanie o używaniu IDisposable (zupełnie nie wiedziałem). Zdałem sobie również sprawę, jak ważne jest ręczne łapanie błędów w innym wątku, ponieważ nie ma kontekstu . pracowałem nad teorią, że powinienem pozwolić elmah zająć się wszystkim. Ponadto dalsza eksploracja uświadomiła mi, że zapomniałem użyć IDisposable również na mailmessage.

W odpowiedzi na Ryszarda, chociaż widzę, że wątek rozwiązanie może działać (zgodnie z sugestią w moim pierwszym przykładzie) tak długo, jak łapię błędy... nadal jest coś strasznego w tym, że IIS całkowicie eksploduje, jeśli ten błąd nie zostanie złapany. To mi mówi, że ASP.NET/IIS nigdy tego nie chciałem... dlatego skłaniam się ku ciągłemu używaniu .BeginInvoke / delegaci zamiast tego, ponieważ to nie psuje IIS, gdy coś idzie nie tak i wydaje się być bardziej popularne w ASP.NET.

W odpowiedzi na ASawyer, byłem całkowicie zaskoczony że było .Sendasync wbudowany w klienta SMTP. Bawiłem się tym rozwiązaniem przez jakiś czas, ale nie wydaje mi się, aby to działało. Chociaż mogę pominąć klienta kodu, który robi SendAsync, strona nadal "czeka" aż Zdarzenie SendCompleted zostanie wykonane. Moim celem było, aby użytkownik i strona poruszały się do przodu, podczas gdy e-mail jest wysyłany w tle. Mam przeczucie, że nadal robię coś złego... więc jeśli ktoś tu przyjdzie, może będzie chciał spróbować. siebie.

Oto moje pełne rozwiązanie, jak wysyłałem e-maile w 100% asynchronicznie oprócz ELMAH.Rejestrowanie błędów MVC. Postanowiłem skorzystać z rozszerzonej wersji przykładu 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.")));
    }
}
 24
Author: Ralph N,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-06 12:50:43

Od. NET 4.5 SmtpClient implementuje metodę asynchroniczną SendMailAsync. W rezultacie wysyłanie wiadomości e-mail asynchronicznie jest następujące:

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);
    }
} 
 5
Author: Boris Lipschitz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-18 06:02:11

Czy używasz. Net SmtpClient do wysyłania wiadomości e-mail? może już wysyłać wiadomości asynchroniczne .

Edit-jeśli {[0] } nie jest wrapperem nad SmtpClient, to chyba nie będzie to tak przydatne.

 3
Author: asawyer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-05 18:14:40

Threading nie jest tu złą opcją, ale jeśli sam nie poradzisz sobie z wyjątkiem, spowoduje to pęknięcie i awarię procesu. Nie ma znaczenia, na którym wątku to robisz.

Więc zamiast Mailera.SendTheMail () try this:

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

Jeszcze lepiej, Użyj asynchronicznych możliwości SmtpClient, jeśli możesz. Nadal będziesz musiał obsługiwać wyjątki.

Sugerowałbym nawet rzucenie okiem na nową bibliotekę Zadań Parallet. Net 4. Który ma dodatkową funkcjonalność co pozwala obsłużyć wyjątkowe przypadki i dobrze współpracuje z ASP.Net Pula wątków.

 3
Author: Richard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-05 18:26:35

Jeśli używasz klas. Net SmtpClient i MailMessage, powinieneś zwrócić uwagę na kilka rzeczy. Po pierwsze, spodziewaj się błędów przy wysyłaniu, więc pułapka i obsłużyć je. Po drugie, w. Net 4 zaszły pewne zmiany w tych klasach, a obie implementują IDisposable(MailMessage od 3.5, SmtpClient nowy w 4.0). Z tego powodu tworzenie SmtpClient i MailMessage powinno być zawinięte w bloki lub jawnie usunięte. Jest to przełomowa zmiana, niektórzy ludzie nie są świadomi z.

Zobacz to pytanie, aby uzyskać więcej informacji na temat usuwania podczas korzystania z asynchronicznych wysyłek:

Jakie są najlepsze praktyki korzystania z SmtpClient, SendAsync i Dispose pod. NET 4.0

 3
Author: hatchet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:37

Dlaczego więc nie mieć osobnego Pollera/serwisu, który zajmuje się wyłącznie wysyłaniem maili? W ten sposób, pozwalając na rejestrację post-back, aby wykonać tylko w czasie potrzebnym do zapisu do bazy danych/kolejki wiadomości i opóźniając wysyłanie wiadomości e-mail do następnego interwału ankiety.

Zastanawiam się nad tym samym problemem właśnie teraz i myślę, że naprawdę nie chcę nawet inicjować wysyłania wiadomości e-mail w ramach żądania zwrotnego po serwerze. Proces obsługi stron WWW powinien być zainteresowany otrzymaniem odpowiedzi do Użytkownika jak najszybciej, im więcej pracy spróbujesz wykonać, tym wolniej będzie.

Spójrz na polecenie Query Segregation Principal ( http://martinfowler.com/bliki/CQRS.html ). Martin Fowler wyjaśnia, że w części poleceń operacji mogą być używane różne modele niż w części zapytań. W tym scenariuszu komendą byłoby "register user", zapytaniem byłoby e-mail aktywacyjny, używając luźnej analogii. Stosowny cytat byłby prawdopodobnie być:

Przez oddzielne modele rozumiemy najczęściej różne modele obiektowe, prawdopodobnie działające w różnych procesach logicznych

Warto też przeczytać artykuł Wikipedii o CQRS ( http://en.wikipedia.org/wiki/Command%E2%80%93query_separation ). ważnym punktem, który to podkreśla, jest:

Jest to ewidentnie zamierzona wskazówka programowa, a nie reguła dobrego kodowania

Znaczenie, użyj go tam, gdzie twój kod, program wykonanie i zrozumienie programisty byłoby korzystne. To dobry przykład.

To podejście ma dodatkową zaletę negowania wszystkich problemów związanych z gwintowaniem mufti i bólów głowy, które mogą przynieść.

 2
Author: A. Murray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-12 09:02:35

Pracowałem nad tym samym problemem dla mojego projektu:

Pierwsza próba {[0] } tak jak ty:
- I loose context
- Problem z obsługą WYJĄTKÓW
- Powszechnie mówi się, Thread są złym pomysłem na IIS threadpool

Więc przełączam się i próbuję z asynchronously:
- 'asynchronicznie' jest fake w asp.net aplikacja internetowa. To po prostu umieścić wywołania kolejki i swicth kontekst

Więc robię usługę windows i pobieram wartości przez tabelę sql: happy end

Więc dla szybkiego rozwiązania: od ajax strony wykonaj wywołanie asynchroniczne powiedz użytkownikowi fake tak, ale kontynuuj Wysyłanie zadania w kontrolerze mvc

 1
Author: asdf_enel_hak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-05 18:29:33

Użyj tej drogi-

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 ";
        }
 1
Author: Rahul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-12 12:34:44

Jeśli chcesz wykryć wycieki, musisz użyć profilera takiego jak ten:

Http://memprofiler.com/

Nie widzę nic złego z Twoim rozwiązaniem, ale mogę prawie zagwarantować, że to pytanie zostanie zamknięte jako subiektywne.

Inną opcją jest użycie jQuery do wywołania ajax do serwera i wywołania przepływu poczty e-mail. W ten sposób interfejs użytkownika nie jest zamknięty.

Powodzenia!

Matt

 0
Author: Matt Cashatt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-05 18:13:32