【问题标题】:What are best practices for using SmtpClient, SendAsync and Dispose under .NET 4.0在 .NET 4.0 下使用 SmtpClient、SendAsync 和 Dispose 的最佳实践是什么
【发布时间】:2011-11-08 17:38:40
【问题描述】:

现在我对如何管理 SmtpClient 感到有些困惑,因为它是一次性的,尤其是当我使用 SendAsync 拨打电话时。大概在 SendAsync 完成之前我不应该调用 Dispose 。但我应该调用它吗(例如,使用“使用”)。该方案是一个 WCF 服务,它在进行呼叫时定期发送电子邮件。大多数计算速度很快,但发送电子邮件可能需要一秒钟左右,所以 Async 会更好。

我应该在每次发送邮件时创建一个新的 SmtpClient 吗?我应该为整个 WCF 创建一个吗?救命!

更新 万一有所不同,每封电子邮件始终是为用户定制的。 WCF 托管在 Azure 上,Gmail 用作邮件程序。

【问题讨论】:

标签: c# .net-4.0 smtpclient


【解决方案1】:

最初的问题是针对 .NET 4 提出的,但如果它对 .NET 4.5 有帮助,则 SmtpClient 实现了异步等待方法 SendMailAsync.

因此,异步发送邮件如下:

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

最好避免使用 SendAsync 方法。

【讨论】:

  • 为什么最好避免它?我认为这取决于要求。
  • SendMailAsync() 无论如何都是 SendAsync() 方法的包装器。 async/await 更加简洁优雅。它将达到完全相同的要求。
  • @RodHartzell 您可以随时使用 .ContinueWith()
  • 使用 using - 或 dispose - 更好还是没有实际区别?在 SendMailAsync 执行之前是否可以在最后一个“使用”块中释放 smtpClient?
  • MailMessage 也应该被处置。
【解决方案2】:

注意:.NET 4.5 SmtpClient 实现了async awaitable 方法SendMailAsync。对于较低版本,请使用SendAsync,如下所述。


您应始终尽早处置IDisposable 实例。在异步调用的情况下,这是消息发送后的回调。

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

SendAsync 不接受回调有点烦人。

【讨论】:

  • 最后一行不应该有'await'吗?
  • await 可用之前没有编写此代码。这是使用事件处理程序的传统回调。如果使用较新的SendMailAsync,则应使用await
  • SmtpException:发送邮件失败。-->System.InvalidOperationException:此时无法启动异步操作。异步操作只能在异步处理程序或模块内或在页面生命周期中的某些事件期间启动。如果在执行页面时发生此异常,请确保将页面标记为 。此异常还可能表示尝试调用“async void”方法,在 ASP.NET 请求处理中通常不支持该方法。相反,异步方法应该返回一个任务,调用者应该等待它。
  • null 作为第二个参数提供给SendAsync(...) 是否安全?
【解决方案3】:

一般来说,IDisposable 对象应该尽快被释放;在对象上实现 IDisposable 旨在传达这样一个事实,即所讨论的类拥有昂贵的资源,这些资源应该被确定性地释放。但是,如果创建这些资源的成本很高,并且您需要构建大量此类对象,那么将一个实例保留在内存中并重用它可能会更好(性能方面)。只有一种方法可以知道这是否有什么不同:分析它!

Re: disposing and Async: 你不能使用using 显然。相反,您通常在 SendCompleted 事件中处理对象:

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

【讨论】:

    【解决方案4】:

    好的,我知道的老问题了。但是当我需要实现类似的东西时,我自己偶然发现了这个。我只是想分享一些代码。

    我正在迭代多个 SmtpClients 以异步发送多个邮件。我的解决方案类似于 TheCodeKing,但我正在处理回调对象。我还将 MailMessage 作为 userToken 传递以在 SendCompleted 事件中获取它,因此我也可以调用 dispose 。像这样:

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

    【讨论】:

    • 最好为每封要发送的电子邮件创建一个新的 SmtpClient 吗?
    • 可以,对于异步发送,只要你在回调中dispose客户端就可以了……
    • 谢谢!并且只是为了简要解释:www.codefrenzy.net/2012/01/30/how-asynchronous-is-smtpclient-sendasync
    • 这是我在 stackoverflow 上找到的关于 smtpclient.sendAsync 函数及其相关处置处理的最简单且准确的答案之一。我写了一个异步批量邮件发送库。由于我每隔几分钟发送 50 多条消息,因此执行 dispose 方法对我来说是非常重要的一步。这段代码正是帮助我实现了这一点。如果我在多线程环境中发现此代码中的一些错误,我会回复。
    • 我可以说,当您循环发送 100 多封电子邮件时,这不是一个好方法,除非您能够配置交换服务器(如果您使用)。服务器可能会抛出类似 4.3.2 The maximum number of concurrent connections has exceeded a limit, closing trasmission channel 的异常。而是尝试仅使用 SmtpClient 的一个实例
    【解决方案5】:

    您可以通过以下评论了解为什么处理 SmtpClient 尤为重要:

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

    在我使用 Gmail 发送多封邮件而不处理客户端的场景中,我曾经得到:

    消息:服务不可用,正在关闭传输通道。这 服务器响应是:4.7.0 临时系统问题。稍后再试 (WS)。 oo3sm17830090pdb.64 - gsmtp

    【讨论】:

    • 感谢您在这里分享您的例外情况,因为到目前为止我正在发送 SMTP 客户端而没有进行处理。虽然我使用的是自己的 SMTP 服务器,但应该始终考虑良好的编程习惯。从您的错误来看,我现在得到了警告,并将纠正我的代码以包含处置函数以确保平台可靠性。
    【解决方案6】:

    我在asp.net 5.0 core中使用过这种方式。

    public async Task EmailSend(MessageModel messageModel)
        {
            using (MailMessage mailMessage = new MailMessage())
            {
                mailMessage.From = new MailAddress(_configuration.GetSection("EmailConfiguration").GetSection("FromEmail").Value.ToString(), _configuration.GetSection("EmailConfiguration").GetSection("FromName").Value.ToString(), Encoding.UTF8);
                mailMessage.Subject = messageModel.Subject;
                mailMessage.SubjectEncoding = Encoding.UTF8;
                mailMessage.Body = messageModel.Content;
                mailMessage.BodyEncoding = Encoding.UTF8;
                mailMessage.IsBodyHtml = true;
                mailMessage.BodyTransferEncoding = TransferEncoding.Base64;
                mailMessage.To.Add(new MailAddress(messageModel.To));
                NetworkCredential networkCredential = new NetworkCredential(_configuration.GetSection("EmailConfiguration").GetSection("Username").Value.ToString(), _configuration.GetSection("EmailConfiguration").GetSection("Password").Value.ToString());
                SmtpClient smtpClient = new SmtpClient();
                smtpClient.Host = _configuration.GetSection("EmailConfiguration").GetSection("SmtpServer").Value.ToString();
                smtpClient.EnableSsl = Convert.ToBoolean(_configuration.GetSection("EmailConfiguration").GetSection("SSL").Value);
                smtpClient.UseDefaultCredentials = Convert.ToBoolean(_configuration.GetSection("EmailConfiguration").GetSection("UseDefaultCredentials").Value);
                smtpClient.Port = Convert.ToInt32(_configuration.GetSection("EmailConfiguration").GetSection("Port").Value);
                smtpClient.Credentials = networkCredential;
                smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
                await smtpClient.SendMailAsync(mailMessage);
            }
        }
    

    【讨论】:

      猜你喜欢
      • 2013-07-25
      • 1970-01-01
      • 2010-09-05
      • 2018-03-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多