Jakie są najważniejsze wskazówki dotyczące używania SmtpClient, SendAsync i Dispose w .NET 4.0

116

Jestem trochę zakłopotany tym, jak zarządzać SmtpClient teraz, gdy jest on jednorazowy, zwłaszcza jeśli wykonuję połączenia za pomocą SendAsync. Przypuszczalnie nie powinienem wywoływać Dispose, dopóki SendAsync nie zakończy się. Ale czy powinienem to kiedykolwiek nazwać (np. Używając „używając”). Scenariusz to usługa WCF, która okresowo wysyła wiadomości e-mail, gdy wykonywane są połączenia. Większość obliczeń jest szybka, ale wysłanie wiadomości e-mail może zająć około sekundy, więc preferowany byłby Async.

Czy powinienem tworzyć nowego SmtpClient za każdym razem, gdy wysyłam pocztę? Czy powinienem utworzyć jeden dla całego programu WCF? Wsparcie!

Aktualizacja Jeśli ma to znaczenie, każdy e-mail jest zawsze dostosowany do użytkownika. Usługa WCF jest hostowana na platformie Azure, a Gmail jest używany jako poczta.

tofutim
źródło
1
Zobacz ten post o większym obrazie obsługi IDisposable i async: stackoverflow.com/questions/974945/ ...
Chris Haas

Odpowiedzi:

139

Uwaga: .NET 4.5 SmtpClient implementuje async awaitablemetodę SendMailAsync. W przypadku niższych wersji użyj SendAsynczgodnie z opisem poniżej.


Zawsze powinieneś IDisposablejak najszybciej pozbywać się instancji. W przypadku wywołań asynchronicznych jest to wywołanie zwrotne po wysłaniu wiadomości.

var message = new MailMessage("from", "to", "subject", "body"))
var client = new SmtpClient("host");
client.SendCompleted += (s, e) => {
                           client.Dispose();
                           message.Dispose();
                        };
client.SendAsync(message, null);

To trochę denerwujące, że SendAsyncnie przyjmuje oddzwonienia.

TheCodeKing
źródło
czy ostatnia linijka nie powinna mieć „czekania”?
niico,
20
Żaden ten kod nie został napisany wcześniej, nie awaitbył dostępny. Jest to tradycyjne wywołanie zwrotne z obsługą zdarzeń. awaitpowinien być używany, jeśli używasz nowszego SendMailAsync.
TheCodeKing
3
SmtpException: Błąd podczas wysyłania poczty. -> System.InvalidOperationException: W tej chwili nie można rozpocząć operacji asynchronicznej. Operacje asynchroniczne można uruchamiać tylko w ramach asynchronicznej procedury obsługi lub modułu lub podczas niektórych zdarzeń w cyklu życia strony. Jeśli ten wyjątek wystąpił podczas wykonywania strony, upewnij się, że strona jest oznaczona jako <% @ Page Async = "true"%>. Ten wyjątek może również wskazywać na próbę wywołania metody „async void”, która zazwyczaj nie jest obsługiwana w ramach przetwarzania żądań ASP.NET. Zamiast tego metoda asynchroniczna powinna zwrócić Task, a obiekt wywołujący powinien na to poczekać.
Mrchief
1
Czy można bezpiecznie podać nulldrugi parametr do SendAsync(...)?
żartobliwy
167

Pierwotne pytanie dotyczyło platformy .NET 4, ale jeśli to pomaga, od wersji .NET 4.5 SmtpClient implementuje metodę async awaitable SendMailAsync.

W rezultacie asynchroniczne wysyłanie wiadomości e-mail wygląda następująco:

public async Task SendEmail(string toEmailAddress, string emailSubject, string emailMessage)
{
    using (var message = new MailMessage())
    {
        message.To.Add(toEmailAddress);

        message.Subject = emailSubject;
        message.Body = emailMessage;

        using (var smtpClient = new SmtpClient())
        {
            await smtpClient.SendMailAsync(message);
        }
    }
}

Lepiej unikać używania metody SendAsync.

Boris Lipschitz
źródło
Dlaczego lepiej tego unikać? Myślę, że to zależy od wymagań.
Jowen
14
SendMailAsync () i tak jest otoką wokół metody SendAsync (). async / await jest o wiele schludniejszy i bardziej elegancki. Osiągnąłby dokładnie te same wymagania.
Boris Lipschitz
2
@RodHartzell, którego zawsze możesz użyć .ContinueWith ()
Boris Lipschitz
2
Czy lepiej jest używać - czy wyrzucać - czy nie ma praktycznej różnicy? Czy nie jest możliwe w tym ostatnim bloku „using”, że smtpClient może zostać usunięty przed wykonaniem SendMailAsync?
niico,
6
MailMessagenależy również usunąć.
TheCodeKing
16

Ogólnie rzecz biorąc, obiekty IDisposable powinny zostać usunięte tak szybko, jak to możliwe; implementacja IDisposable na obiekcie ma na celu zakomunikowanie faktu, że dana klasa zawiera kosztowne zasoby, które powinny być deterministycznie uwalniane. Jeśli jednak tworzenie tych zasobów jest kosztowne i musisz skonstruować wiele z tych obiektów, może być lepiej (z punktu widzenia wydajności) zachować jedną instancję w pamięci i użyć jej ponownie. Jest tylko jeden sposób, aby dowiedzieć się, czy to robi jakąkolwiek różnicę: profiluj to!

Re: disposing and Async: usingoczywiście nie możesz użyć . Zamiast tego zazwyczaj usuwasz obiekt w zdarzeniu SendCompleted:

var smtpClient = new SmtpClient();
smtpClient.SendCompleted += (s, e) => smtpClient.Dispose();
smtpClient.SendAsync(...);
jeroenh
źródło
6

Ok, stare pytanie, które znam. Ale sam się na to natknąłem, gdy potrzebowałem zaimplementować coś podobnego. Chciałem tylko udostępnić jakiś kod.

Iteruję przez kilka SmtpClients, aby wysłać kilka wiadomości e-mail asynchronicznie. Moje rozwiązanie jest podobne do TheCodeKing, ale zamiast tego usuwam obiekt wywołania zwrotnego. Przekazuję również MailMessage jako userToken, aby uzyskać go w zdarzeniu SendCompleted, więc mogę również wywołać dispose. Lubię to:

foreach (Customer customer in Customers)
{
    SmtpClient smtpClient = new SmtpClient(); //SmtpClient configuration out of this scope
    MailMessage message = new MailMessage(); //MailMessage configuration out of this scope

    smtpClient.SendCompleted += (s, e) =>
    {
        SmtpClient callbackClient = s as SmtpClient;
        MailMessage callbackMailMessage = e.UserState as MailMessage;
        callbackClient.Dispose();
        callbackMailMessage.Dispose();
    };

    smtpClient.SendAsync(message, message);
}
jmelhus
źródło
2
Czy najlepszą praktyką jest tworzenie nowego SmtpClient dla każdego wysłanego e-maila?
Martín Coll
1
Tak, do wysyłania asynchronicznego, o ile
usuwasz
1
dzięki! i tylko dla krótkiego wyjaśnienia: www.codefrenzy.net/2012/01/30/how-asynchronous-is-smtpclient-sendasync
Martín Coll
1
Jest to jedna z najprostszych i najdokładniejszych odpowiedzi, jakie znalazłem w stackoverflow dla funkcji smtpclient.sendAsync i związanej z nią obsługi usuwania. Napisałem asynchroniczną bibliotekę do masowego wysyłania poczty. Ponieważ co kilka minut wysyłam wiadomość 50+, wykonanie metody usuwania było dla mnie bardzo ważnym krokiem. Ten kod dokładnie mi w tym pomógł. Odpowiem w przypadku znalezienia błędów w tym kodzie podczas środowisk wielowątkowych.
vibs2006,
1
Mogę powiedzieć, że nie jest to dobre podejście, gdy wysyłasz 100+ e-maili w pętli, chyba że masz możliwość skonfigurowania serwera wymiany (jeśli używasz). Serwer może zgłosić wyjątek, taki jak 4.3.2 The maximum number of concurrent connections has exceeded a limit, closing trasmission channel. Zamiast tego spróbuj użyć tylko jednego wystąpieniaSmtpClient
ibubi
6

Możesz zobaczyć, dlaczego pozbycie się SmtpClient jest szczególnie ważne, po następującym komentarzu:

public class SmtpClient : IDisposable
   // Summary:
    //     Sends a QUIT message to the SMTP server, gracefully ends the TCP connection,
    //     and releases all resources used by the current instance of the System.Net.Mail.SmtpClient
    //     class.
    public void Dispose();

W moim scenariuszu wysyłania wielu wiadomości za pomocą Gmaila bez usuwania klienta otrzymywałem:

Komunikat: Usługa niedostępna, zamykanie kanału transmisji. Odpowiedź serwera brzmiała: 4.7.0 Tymczasowy problem z systemem. Spróbuj ponownie później (WS). oo3sm17830090pdb.64 - gsmtp

Anton Skovorodko
źródło
1
Dziękuję za udostępnienie tutaj swojego wyjątku, ponieważ wysyłałem klientów SMTP bez usuwania do tej pory. Chociaż używam własnego serwera SMTP, ale zawsze należy wziąć pod uwagę dobrą praktykę programowania. Patrząc na twój błąd, mam teraz ostrzeżenia i poprawię swój kod, aby zawierał funkcje usuwania, aby zapewnić niezawodność platformy.
vibs2006,