Quali sono le migliori pratiche per l'utilizzo di SmtpClient, SendAsync e Dispose in .NET 4.0


116

Sono un po 'perplesso su come gestire SmtpClient ora che è usa e getta, soprattutto se effettuo chiamate utilizzando SendAsync. Presumibilmente non dovrei chiamare Dispose fino al completamento di SendAsync. Ma dovrei mai chiamarlo (ad esempio, usando "using"). Lo scenario è un servizio WCF che invia periodicamente messaggi di posta elettronica quando vengono effettuate le chiamate. La maggior parte del calcolo è veloce, ma l'invio di e-mail può richiedere circa un secondo, quindi Async sarebbe preferibile.

Devo creare un nuovo SmtpClient ogni volta che invio la posta? Devo crearne uno per l'intero WCF? Aiuto!

Aggiorna Nel caso in cui faccia la differenza, ogni email è sempre personalizzata per l'utente. Il WCF è ospitato su Azure e Gmail viene utilizzato come mailer.


1
Guarda questo post sul quadro più ampio su come gestire IDisposable e async: stackoverflow.com/questions/974945/…
Chris Haas

Risposte:


139

Nota: .NET 4.5 SmtpClient implementa il async awaitablemetodo SendMailAsync. Per le versioni inferiori, utilizzare SendAsynccome descritto di seguito.


Dovresti sempre smaltire le IDisposableistanze il prima possibile. Nel caso di chiamate asincrone, questa è sulla richiamata dopo l'invio del messaggio.

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

È un po 'fastidioso il SendAsyncnon accettare una richiamata.


l'ultima riga non dovrebbe avere "wait"?
niico

20
No, questo codice è stato scritto prima che awaitfosse disponibile. Si tratta di una richiamata tradizionale che utilizza gestori di eventi. awaitdovrebbe essere utilizzato se si utilizza il più recente SendMailAsync.
TheCodeKing

3
SmtpException: errore durante l'invio della posta. -> System.InvalidOperationException: non è possibile avviare un'operazione asincrona in questo momento. Le operazioni asincrone possono essere avviate solo all'interno di un gestore o modulo asincrono o durante determinati eventi nel ciclo di vita della pagina. Se questa eccezione si è verificata durante l'esecuzione di una pagina, assicurati che la pagina sia contrassegnata con <% @ Page Async = "true"%>. Questa eccezione può anche indicare un tentativo di chiamare un metodo "async void", che generalmente non è supportato nell'elaborazione delle richieste ASP.NET. Invece, il metodo asincrono dovrebbe restituire un Task e il chiamante dovrebbe attenderlo.
Mrchief

1
È sicuro fornire nullcome secondo parametro a SendAsync(...)?
jocull

167

La domanda originale è stata posta per .NET 4, ma se aiuta a partire da .NET 4.5 SmtpClient implementa il metodo di attesa asincrono SendMailAsync.

Di conseguenza, inviare messaggi di posta elettronica in modo asincrono è il seguente:

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

È meglio evitare di utilizzare il metodo SendAsync.


Perché è meglio evitarlo? Penso che dipenda dai requisiti.
Jowen

14
SendMailAsync () è comunque un wrapper per il metodo SendAsync (). async / await è molto più ordinato ed elegante. Raggiungerebbe esattamente gli stessi requisiti.
Boris Lipschitz

2
@RodHartzell puoi sempre usare .ContinueWith ()
Boris Lipschitz

2
È meglio usare usando - o smaltire - o nessuna differenza pratica? Non è possibile in quell'ultimo blocco "using" che smtpClient possa essere eliminato prima che SendMailAsync sia stato eseguito?
niico

6
MailMessagedovrebbe anche essere smaltito.
TheCodeKing

16

In generale, gli oggetti IDisposable dovrebbero essere smaltiti il ​​prima possibile; l'implementazione di IDisposable su un oggetto ha lo scopo di comunicare il fatto che la classe in questione contiene risorse costose che dovrebbero essere rilasciate in modo deterministico. Tuttavia, se la creazione di queste risorse è costosa ed è necessario costruire molti di questi oggetti, potrebbe essere meglio (in termini di prestazioni) mantenere un'istanza in memoria e riutilizzarla. C'è solo un modo per sapere se questo fa la differenza: profilalo!

Re: disposing e Async: usingovviamente non puoi usare . Invece in genere si elimina l'oggetto nell'evento SendCompleted:

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

6

Ok, vecchia domanda lo so. Ma mi sono imbattuto in questo io stesso quando avevo bisogno di implementare qualcosa di simile. Volevo solo condividere del codice.

Sto iterando su diversi SmtpClient per inviare diversi messaggi in modo asincrono. La mia soluzione è simile a TheCodeKing, ma invece sto eliminando l'oggetto callback. Sto anche passando MailMessage come userToken per ottenerlo nell'evento SendCompleted in modo da poter chiamare dispose anche su quello. Come questo:

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

2
È consigliabile creare un nuovo SmtpClient per ogni e-mail da inviare?
Martín Coll

1
Sì, per l'invio asincrono, a condizione che tu disponga del client nella richiamata ...
jmelhus

1
Grazie! e solo per una breve spiegazione: www.codefrenzy.net/2012/01/30/how-asynchronous-is-smtpclient-sendasync
Martín Coll

1
Questa è una delle risposte più semplici e accurate che ho trovato su stackoverflow per la funzione smtpclient.sendAsync e la relativa gestione dello smaltimento. Ho scritto una libreria per l'invio di posta in blocco asincrona. Poiché invio più di 50 messaggi ogni pochi minuti, l'esecuzione del metodo di eliminazione è stato un passaggio molto importante per me. Questo codice mi ha esattamente aiutato a raggiungere questo obiettivo. Risponderò nel caso avessi riscontrato qualche bug in questo codice durante gli ambienti multi threading.
vibs2006

1
Posso dire che non è un buon approccio quando invii più di 100 e-mail in un ciclo a meno che tu non abbia la capacità di configurare il server di scambio (se lo usi). Il server potrebbe generare un'eccezione come 4.3.2 The maximum number of concurrent connections has exceeded a limit, closing trasmission channel. Prova invece a utilizzare solo un'istanza diSmtpClient
ibubi

6

Puoi capire perché è particolarmente importante smaltire SmtpClient dal seguente commento:

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

Nel mio scenario l'invio di più messaggi di posta utilizzando Gmail senza eliminare il client, ero solito ottenere:

Messaggio: servizio non disponibile, chiusura canale di trasmissione. La risposta del server è stata: 4.7.0 Problema di sistema temporaneo. Riprova più tardi (WS). oo3sm17830090pdb.64 - gsmtp


1
Grazie per aver condiviso la tua eccezione qui poiché stavo inviando client SMTP senza eliminarli finora. Anche se sto utilizzando il mio server SMTP, è sempre opportuno prendere in considerazione una buona pratica di programmazione. Guardando dal tuo errore, ora ho ricevuto avvertenze e rettificherò il mio codice per includere funzioni di dispose per garantire l'affidabilità della piattaforma.
vibs2006
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.