Quais são as práticas recomendadas para usar SmtpClient, SendAsync e Dispose no .NET 4.0

116

Estou um pouco perplexo sobre como gerenciar SmtpClient agora que ele é descartável, especialmente se eu fizer chamadas usando SendAsync. Presumivelmente, não devo chamar Dispose até que SendAsync seja concluído. Mas devo chamá-lo (por exemplo, usando "usando"). O cenário é um serviço WCF que envia e-mails periodicamente quando as chamadas são feitas. A maior parte do cálculo é rápida, mas o envio de e-mail pode levar cerca de um segundo, portanto, o Async seria preferível.

Devo criar um novo SmtpClient cada vez que enviar um e-mail? Devo criar um para todo o WCF? Socorro!

Atualizar Caso faça diferença, cada e-mail é sempre customizado para o usuário. O WCF é hospedado no Azure e o Gmail é usado como remetente.

tofutim
fonte
1
Veja este post sobre o panorama geral de como lidar com IDisposable e assíncrono: stackoverflow.com/questions/974945/…
Chris Haas

Respostas:

139

Nota: .NET 4.5 SmtpClient implementa o async awaitablemétodo SendMailAsync. Para versões anteriores, use SendAsyncconforme descrito abaixo.


Você deve sempre descartar as IDisposableinstâncias o mais rápido possível. No caso de chamadas assíncronas, isso ocorre no retorno de chamada após o envio da mensagem.

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

É um pouco chato SendAsyncporque não aceita uma chamada de retorno.

TheCodeKing
fonte
a última linha não deveria ter 'esperar'?
niico
19
Nenhum, este código foi escrito antes de awaitestar disponível. Este é um retorno de chamada tradicional usando manipuladores de eventos. awaitdeve ser usado se estiver usando o mais recente SendMailAsync.
TheCodeKing
3
SmtpException: Falha no envio de email .--> System.InvalidOperationException: Uma operação assíncrona não pode ser iniciada neste momento. As operações assíncronas só podem ser iniciadas em um manipulador ou módulo assíncrono ou durante certos eventos no ciclo de vida da página. Se essa exceção ocorreu durante a execução de uma página, certifique-se de que a página esteja marcada como <% @ Page Async = "true"%>. Essa exceção também pode indicar uma tentativa de chamar um método "void assíncrono", que geralmente não tem suporte no processamento de solicitações ASP.NET. Em vez disso, o método assíncrono deve retornar uma Tarefa e o chamador deve aguardar por ela.
Mrchief,
1
É seguro fornecer nullcomo o segundo parâmetro para SendAsync(...)?
jocull
167

A pergunta original foi feita para o .NET 4, mas se ajudar a partir do .NET 4.5, SmtpClient implementa o método assíncrono aguardável SendMailAsync.

Como resultado, o envio de e-mail de forma assíncrona é o seguinte:

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

É melhor evitar o uso do método SendAsync.

Boris Lipschitz
fonte
Por que é melhor evitá-lo? Acho que depende dos requisitos.
Jowen
14
SendMailAsync () é um wrapper em torno do método SendAsync () de qualquer maneira. async / await é muito mais puro e elegante. Cumpriria exatamente os mesmos requisitos.
Boris Lipschitz
2
@RodHartzell você sempre pode usar .ContinueWith ()
Boris Lipschitz
2
É melhor usar usando - ou descartar - ou nenhuma diferença prática? Não é possível naquele último bloco 'usando' que smtpClient pudesse ser descartado antes de SendMailAsync ser executado?
niico
6
MailMessagetambém deve ser descartado.
TheCodeKing
16

Em geral, os objetos IDisposable devem ser descartados o mais rápido possível; implementar IDisposable em um objeto tem como objetivo comunicar o fato de que a classe em questão contém recursos caros que deveriam ser liberados de forma determinística. No entanto, se criar esses recursos for caro e você precisar construir muitos desses objetos, pode ser melhor (em termos de desempenho) manter uma instância na memória e reutilizá-la. Só há uma maneira de saber se isso faz alguma diferença: crie um perfil!

Re: descarte e assíncrono: você não pode usar usingobviamente. Em vez disso, você normalmente descarta o objeto no evento SendCompleted:

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

Ok, velha pergunta eu sei. Mas me deparei com isso quando precisava implementar algo semelhante. Eu só queria compartilhar um código.

Estou iterando em vários SmtpClients para enviar vários emails de forma assíncrona. Minha solução é semelhante a TheCodeKing, mas estou descartando o objeto de retorno de chamada. Também estou passando MailMessage como userToken para obtê-lo no evento SendCompleted, para que eu possa chamar dispose nisso também. Como isso:

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
fonte
2
É a prática recomendada criar um novo SmtpClient para cada e-mail a ser enviado?
Martín Coll de
1
Sim, para envio assíncrono, desde que você descarte o cliente no retorno de chamada ...
jmelhus
1
obrigado! e apenas para uma breve explicação: www.codefrenzy.net/2012/01/30/how-asynchronous-is-smtpclient-sendasync
Martín Coll
1
Este é um dos mais simples e precisos respostas que encontrei no stackoverflow para a função smtpclient.sendAsync e seu tratamento de descarte relacionado. Eu escrevi uma biblioteca de envio de email em massa assíncrona. Como eu envio mais de 50 mensagens a cada poucos minutos, executar o método de descarte foi uma etapa muito importante para mim. Esse código exatamente me ajudou a conseguir isso. Vou responder caso tenha encontrado alguns bugs neste código durante os ambientes de multiencadeamento.
vibs2006
1
Posso dizer que não é uma boa abordagem quando você envia mais de 100 e-mails em um loop, a menos que tenha a capacidade de configurar o servidor Exchange (se usar). O servidor pode lançar uma exceção como 4.3.2 The maximum number of concurrent connections has exceeded a limit, closing trasmission channel. Em vez disso, tente usar apenas uma instância deSmtpClient
ibubi
6

Você pode ver por que é particularmente importante descartar SmtpClient com o seguinte comentário:

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

No meu cenário de envio de vários e-mails usando o Gmail sem descartar o cliente, eu costumava obter:

Mensagem: Serviço indisponível, fechando canal de transmissão. A resposta do servidor foi: 4.7.0 Problema Temporário do Sistema. Tente novamente mais tarde (WS). oo3sm17830090pdb.64 - gsmtp

Anton Skovorodko
fonte
1
Obrigado por compartilhar sua exceção aqui, pois eu estava enviando clientes SMTP sem descartar até agora. Embora eu esteja usando meu próprio servidor SMTP, uma boa prática de programação deve ser sempre considerada. Com base no seu erro, agora recebi cuidados e retificarei meu código para incluir funções de descarte para garantir a confiabilidade da plataforma.
vibs2006