Quelles sont les meilleures pratiques pour l'utilisation de SmtpClient, SendAsync et Dispose sous .NET 4.0

116

Je suis un peu perplexe sur la façon de gérer SmtpClient maintenant qu'il est jetable, surtout si je passe des appels en utilisant SendAsync. Je ne devrais probablement pas appeler Dispose avant la fin de SendAsync. Mais devrais-je jamais l'appeler (par exemple, en utilisant "using"). Le scénario est un service WCF qui envoie régulièrement des e-mails lorsque des appels sont effectués. La plupart des calculs sont rapides, mais l'envoi d'e-mails peut prendre une seconde environ, donc Async serait préférable.

Dois-je créer un nouveau SmtpClient à chaque fois que j'envoie du courrier? Dois-je en créer un pour l'ensemble de la WCF? Aidez-moi!

Mise à jour Au cas où cela ferait une différence, chaque e-mail est toujours personnalisé pour l'utilisateur. Le WCF est hébergé sur Azure et Gmail est utilisé comme messagerie.

tofutim
la source
1
Voir cet article sur la façon de gérer IDisposable et async: stackoverflow.com/questions/974945/…
Chris Haas

Réponses:

139

Remarque: .NET 4.5 SmtpClient implémente la async awaitableméthode SendMailAsync. Pour les versions inférieures, utilisez SendAsynccomme décrit ci-dessous.


Vous devez toujours supprimer les IDisposableinstances le plus tôt possible. Dans le cas des appels asynchrones, il s'agit du rappel après l'envoi du message.

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

C'est un peu ennuyeux que SendAsyncn'accepte pas de rappel.

TheCodeKing
la source
la dernière ligne ne devrait-elle pas avoir «attendre»?
niico
20
Non, ce code a été écrit avant awaitétait disponible. Il s'agit d'un rappel traditionnel utilisant des gestionnaires d'événements. awaitdoit être utilisé si vous utilisez le plus récent SendMailAsync.
TheCodeKing
3
SmtpException: échec de l'envoi du courrier .--> System.InvalidOperationException: une opération asynchrone ne peut pas être démarrée pour le moment. Les opérations asynchrones ne peuvent être démarrées que dans un gestionnaire ou un module asynchrone ou pendant certains événements du cycle de vie de la page. Si cette exception s'est produite lors de l'exécution d'une page, assurez-vous que la page est marquée <% @ Page Async = "true"%>. Cette exception peut également indiquer une tentative d'appeler une méthode «async void», qui n'est généralement pas prise en charge dans le traitement des requêtes ASP.NET. Au lieu de cela, la méthode asynchrone doit renvoyer une tâche et l'appelant doit l'attendre.
Mrchief
1
Est-il sûr de fournir nullcomme deuxième paramètre à SendAsync(...)?
jocull
167

La question d'origine a été posée pour .NET 4, mais si cela aide à partir de .NET 4.5, SmtpClient implémente la méthode attendable asynchrone SendMailAsync .

Par conséquent, pour envoyer des e-mails de manière asynchrone, procédez comme suit:

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

Il vaut mieux éviter d'utiliser la méthode SendAsync.

Boris Lipschitz
la source
Pourquoi vaut-il mieux l'éviter? Je pense que cela dépend des exigences.
Jowen
14
SendMailAsync () est de toute façon un wrapper autour de la méthode SendAsync (). async / await est bien plus soigné et plus élégant. Cela répondrait exactement aux mêmes exigences.
Boris Lipschitz
2
@RodHartzell, vous pouvez toujours utiliser .ContinueWith ()
Boris Lipschitz
2
Vaut-il mieux utiliser - ou éliminer - ou pas de différence pratique? N'est-il pas possible dans ce dernier bloc 'using' que smtpClient puisse être supprimé avant que SendMailAsync ne soit exécuté?
niico
6
MailMessagedoit également être éliminé.
TheCodeKing
16

En général, les objets IDisposables doivent être éliminés dès que possible; l'implémentation de IDisposable sur un objet est destinée à communiquer le fait que la classe en question contient des ressources coûteuses qui devraient être libérées de manière déterministe. Cependant, si la création de ces ressources coûte cher et que vous devez construire beaucoup de ces objets, il peut être préférable (en termes de performances) de conserver une instance en mémoire et de la réutiliser. Il n'y a qu'une seule façon de savoir si cela fait une différence: profiler!

Re: disposer et Async: vous ne pouvez pas utiliser usingévidemment. Au lieu de cela, vous supprimez généralement l'objet dans l'événement SendCompleted:

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

Ok, vieille question que je connais. Mais je suis tombé sur cela moi-même lorsque j'avais besoin de mettre en œuvre quelque chose de similaire. Je voulais juste partager du code.

J'itère plusieurs SmtpClients pour envoyer plusieurs e-mails de manière asynchrone. Ma solution est similaire à TheCodeKing, mais je supprime l'objet de rappel à la place. Je passe également MailMessage en tant que userToken pour l'obtenir dans l'événement SendCompleted afin que je puisse également appeler Dispose sur cela. Comme ça:

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
la source
2
Est-il recommandé de créer un nouveau SmtpClient pour chaque e-mail à envoyer?
Martín Coll
1
Oui, pour l'envoi asynchrone, tant que vous disposez du client dans le rappel ...
jmelhus
1
Merci! et juste pour une brève explication: www.codefrenzy.net/2012/01/30/how-asynchronous-is-smtpclient-sendasync
Martín Coll
1
C'est l'une des réponses les plus simples et les plus précises que j'ai trouvées sur stackoverflow pour la fonction smtpclient.sendAsync et sa gestion d'élimination associée. J'ai écrit une bibliothèque d'envoi d'e-mails en masse asynchrone. Comme j'envoie plus de 50 messages toutes les quelques minutes, l'exécution de la méthode dispose était une étape très importante pour moi. Ce code m'a exactement aidé à y parvenir. Je répondrai au cas où je trouverais des bogues dans ce code lors des environnements multi threading.
vibs2006
1
Je peux dire que ce n'est pas une bonne approche lorsque vous envoyez plus de 100 e-mails en boucle, sauf si vous avez la possibilité de configurer le serveur d'échange (si vous l'utilisez). Le serveur peut lancer une exception comme 4.3.2 The maximum number of concurrent connections has exceeded a limit, closing trasmission channel. Essayez plutôt de n'utiliser qu'une seule instance deSmtpClient
ibubi
6

Vous pouvez voir pourquoi il est particulièrement important de se débarrasser de SmtpClient par le commentaire suivant:

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

Dans mon scénario d'envoi de plusieurs e-mails à l'aide de Gmail sans supprimer le client, j'avais l'habitude d'obtenir:

Message: Service non disponible, fermeture du canal de transmission. La réponse du serveur fut: 4.7.0 Problème système temporaire. Réessayez plus tard (WS). oo3sm17830090pdb.64 - gsmtp

Anton Skovorodko
la source
1
Merci de partager votre exception ici car j'envoyais des clients SMTP sans en disposer jusqu'à présent. Bien que j'utilise mon propre serveur SMTP, une bonne pratique de programmation doit toujours être considérée. En regardant votre erreur, j'ai maintenant reçu des avertissements et je rectifierai mon code pour inclure des fonctions de suppression pour assurer la fiabilité de la plate-forme.
vibs2006