Was sind Best Practices für die Verwendung von SmtpClient, SendAsync und Dispose unter .NET 4.0?

116

Ich bin etwas ratlos darüber, wie SmtpClient jetzt verwaltet wird, da es verfügbar ist, insbesondere wenn ich mit SendAsync telefoniere. Vermutlich sollte ich Dispose nicht aufrufen, bis SendAsync abgeschlossen ist. Aber sollte ich es jemals nennen (zB mit "using"). Das Szenario ist ein WCF-Dienst, der E-Mails regelmäßig versendet, wenn Anrufe getätigt werden. Der Großteil der Berechnung ist schnell, aber das Senden von E-Mails kann ungefähr eine Sekunde dauern, daher wäre Async vorzuziehen.

Sollte ich jedes Mal, wenn ich E-Mails sende, einen neuen SmtpClient erstellen? Soll ich eine für die gesamte WCF erstellen? Hilfe!

Update Falls es einen Unterschied macht, wird jede E-Mail immer an den Benutzer angepasst. Die WCF wird in Azure gehostet und Google Mail wird als Mailer verwendet.

tofutim
quelle
1
Lesen Sie diesen Beitrag über das Gesamtbild des Umgangs mit IDisposable und Async: stackoverflow.com/questions/974945/…
Chris Haas

Antworten:

139

Hinweis: .NET 4.5 SmtpClient implementiert die async awaitableMethode SendMailAsync. Verwenden Sie für niedrigere Versionen SendAsyncwie unten beschrieben.


Sie sollten IDisposableInstanzen immer zum frühestmöglichen Zeitpunkt entsorgen . Bei asynchronen Anrufen erfolgt dies beim Rückruf, nachdem die Nachricht gesendet wurde.

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);

Es ist ein bisschen nervig, SendAsyncdass kein Rückruf akzeptiert wird.

TheCodeKing
quelle
Sollte die letzte Zeile nicht "warten"?
Niico
19
Nein, dieser Code wurde geschrieben, bevor er awaitverfügbar war. Dies ist ein traditioneller Rückruf mit Ereignishandlern. awaitsollte verwendet werden, wenn die neuere verwendet wird SendMailAsync.
TheCodeKing
3
SmtpException: Fehler beim Senden von E-Mails .--> System.InvalidOperationException: Eine asynchrone Operation kann derzeit nicht gestartet werden. Asynchrone Vorgänge können nur innerhalb eines asynchronen Handlers oder Moduls oder während bestimmter Ereignisse im Seitenlebenszyklus gestartet werden. Wenn diese Ausnahme beim Ausführen einer Seite aufgetreten ist, stellen Sie sicher, dass die Seite mit <% @ Page Async = "true"%> markiert ist. Diese Ausnahme kann auch auf einen Versuch hinweisen, eine "async void" -Methode aufzurufen, die in der ASP.NET-Anforderungsverarbeitung im Allgemeinen nicht unterstützt wird. Stattdessen sollte die asynchrone Methode eine Aufgabe zurückgeben und der Aufrufer sollte darauf warten.
Mrchief
1
Ist es sicher, nullals zweiten Parameter anzugeben SendAsync(...)?
Jocull
166

Die ursprüngliche Frage wurde für .NET 4 gestellt, aber wenn es ab .NET 4.5 hilft, implementiert SmtpClient eine asynchrone erwartete Methode SendMailAsync .

Das asynchrone Senden von E-Mails ist daher wie folgt:

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);
        }
    }
}

Es ist besser, die SendAsync-Methode zu vermeiden.

Boris Lipschitz
quelle
Warum ist es besser, es zu vermeiden? Ich denke, das hängt von den Anforderungen ab.
Jowen
14
SendMailAsync () ist sowieso ein Wrapper um die SendAsync () -Methode. async / await ist viel ordentlicher und eleganter. Es würde genau die gleichen Anforderungen erfüllen.
Boris Lipschitz
2
@ RodHartzell können Sie immer verwenden .ContinueWith ()
Boris Lipschitz
2
Ist es besser, zu verwenden - oder zu entsorgen - oder keinen praktischen Unterschied? Ist es nicht möglich, dass smtpClient in diesem letzten 'using'-Block entsorgt werden kann, bevor SendMailAsync ausgeführt wurde?
Niico
6
MailMessagesollte auch entsorgt werden.
TheCodeKing
16

Im Allgemeinen sollten IDisposable-Objekte so schnell wie möglich entsorgt werden. Die Implementierung von IDisposable für ein Objekt soll die Tatsache kommunizieren, dass die betreffende Klasse teure Ressourcen enthält, die deterministisch freigegeben werden sollten. Wenn das Erstellen dieser Ressourcen jedoch teuer ist und Sie viele dieser Objekte erstellen müssen, ist es möglicherweise besser (in Bezug auf die Leistung), eine Instanz im Speicher zu belassen und wiederzuverwenden. Es gibt nur einen Weg zu wissen, ob das einen Unterschied macht: Profilieren Sie es!

Re: Entsorgen und Async: Sie können nicht usingoffensichtlich verwenden. Stattdessen entsorgen Sie das Objekt normalerweise im Ereignis SendCompleted:

var smtpClient = new SmtpClient();
smtpClient.SendCompleted += (s, e) => smtpClient.Dispose();
smtpClient.SendAsync(...);
jeroenh
quelle
6

Ok, alte Frage, die ich kenne. Aber ich bin selbst darauf gestoßen, als ich etwas Ähnliches implementieren musste. Ich wollte nur etwas Code teilen.

Ich iteriere über mehrere SmtpClients, um mehrere E-Mails asynchron zu senden. Meine Lösung ähnelt TheCodeKing, aber ich entsorge stattdessen das Rückrufobjekt. Ich übergebe MailMessage auch als userToken, um es im SendCompleted-Ereignis abzurufen, damit ich auch dispose aufrufen kann. So was:

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
quelle
2
Ist es die beste Vorgehensweise, für jede zu sendende E-Mail einen neuen SmtpClient zu erstellen?
Martín Coll
1
Ja, für asynchrones Senden, solange Sie den Client im Rückruf entsorgen ...
jmelhus
1
Vielen Dank! und nur zur kurzen Erklärung: www.codefrenzy.net/2012/01/30/how-asynchronous-is-smtpclient-sendasync
Martín Coll
1
Dies ist eine der einfachsten und genauesten Antworten, die ich im Stackoverflow für die Funktion smtpclient.sendAsync und die damit verbundene Entsorgungsbehandlung gefunden habe. Ich habe eine asynchrone Bulk-Mail-Sendebibliothek geschrieben. Da ich alle paar Minuten mehr als 50 Nachrichten sende, war die Ausführung der Entsorgungsmethode für mich ein sehr wichtiger Schritt. Dieser Code hat mir genau dabei geholfen. Ich werde antworten, falls ich in den Multithreading-Umgebungen einige Fehler in diesem Code gefunden habe.
vibs2006
1
Ich kann sagen, dass dies kein guter Ansatz ist, wenn Sie mehr als 100 E-Mails in einer Schleife senden, es sei denn, Sie können den Exchange-Server konfigurieren (falls Sie ihn verwenden). Server könnte Ausnahme wie auslösen 4.3.2 The maximum number of concurrent connections has exceeded a limit, closing trasmission channel. Versuchen Sie stattdessen, nur eine Instanz vonSmtpClient
ibubi
6

Sie können anhand des folgenden Kommentars erkennen, warum es besonders wichtig ist, SmtpClient zu entsorgen:

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();

In meinem Szenario, in dem ich mehrere E-Mails mit Google Mail gesendet habe, ohne den Client zu entsorgen, habe ich Folgendes erhalten:

Meldung: Dienst nicht verfügbar, Übertragungskanal geschlossen. Die Serverantwort lautete: 4.7.0 Temporäres Systemproblem. Versuchen Sie es später erneut (WS). oo3sm17830090pdb.64 - gsmtp

Anton Skovorodko
quelle
1
Vielen Dank, dass Sie Ihre Ausnahme hier geteilt haben, da ich SMTP-Clients gesendet habe, ohne bisher darüber zu verfügen. Obwohl ich meinen eigenen SMTP-Server verwende, sollte immer eine gute Programmierpraxis berücksichtigt werden. Aufgrund Ihres Fehlers habe ich jetzt Vorsichtsmaßnahmen erhalten und werde meinen Code korrigieren, um Entsorgungsfunktionen einzuschließen, um die Zuverlässigkeit der Plattform sicherzustellen.
Vibs2006