我开发的一个发送邮件的小组件(代码在博文开始处已给出下载地址) 为了简化邮件发送代码编写和SmtpClient实例的管理,我封装了一个发邮件的帮助类。 这个帮助类,包含如图几个文件:
两个主要类: SmtpHelper 和MailHelper 1. SmtpHelper 此类是为了简化构造SmtpClient实例所需的代码量。通过SmtpHelper构造函数设置好SMTP服务器、端口号、身份凭据,再通过链式操作快速设置SmtpClient其他不常使用的属性。 Eg:
使用SmtpHelper类注意事项: 1) 非线程安全类. 2) 构造的SmtpClient 实例外部进行Dispose()。SmtpHelper类只简单提供构造,不做释放操作。 3) SmtpClient 没有提供 Finalize() 终结器,所以GC不会进行回收,只能由外部使用完后进行显示释放,否则会发生内存泄露问题.
2. MailHelper 此类完成邮件的发送工作。需要结合MailInfoHelper静态类验证邮件信息的有效性。 1) 支持快捷添加附件、内嵌资源、地址信息、备用视图格式; Eg:添加内嵌资源
2) 支持在发送邮件前对邮件信息有效性进行检查;
3) 支持批量同步、异步发送邮件 a) 批量同步发送邮件:实际上只是 SmtpClient.Send() 同步发送邮件的一个封装。 b) 批量异步发送邮件 i. 待发送队列:因为一个SmtpClient一次只能发送一个MailMessage,不管是同步还是异步发送,所以 SmtpClient.SendAsync() 方法后必须阻塞线程直到上一封邮件发送完成,否则会抛出“正在发送邮件”的异常。所以,MailHelper为了避免调用线程的阻塞,将待发送邮件的信息都加入到队列中,内部启用一个线程去执行串行化发送任务。 ii. 限流:“异步”批量发送过程中,为了防止待发送队列无限制的增大,导致内存溢出,我们可以通过MailHelper的GetAwaitMailCountAsync()方法监控该队列的大小,适当的执行Thread.Sleep(time). iii. 异步取消:可以通过MailHelper的SendAsyncCancel()方法,取消待发送队列中的邮件继续发送。 iv. 回调函数:异步发送完一封电子邮件后执行的回调函数。通过SendCompleted事件进行注册。但要注意其AsyncCompletedEventArgs参数的UserState对象被改写为了我定义的 MailUserState 对象 MailUserState定义如下:
批量异步发送示例(注意回调函数的用户信息):
4) 批量发送邮件中,每调用一次发送方法,要使用MailHelper的Reset()对邮件内容进行重置。 注意: a) 不重置SmtpClient。SmtpClient根据 m_autoDisposeSmtp 参数自动释放或由外部主动释放 b) 不重置:异步待发送队列及队列计数、AutoResetEvent实例、执行异步发送线程变量、是否启用异步发送标识变量 5) 支持自动释放SmtpClient实例 在平常邮件开发中,当在异步批量发送邮件时,我们没办法掌握何时释放我们重用的SmtpClient实例。 但,我们使用MailHelper类,可以不用关心SmtpClient的释放问题。我们通过构造函数中指定自动释放SmtpClient的参数为true,并且统计好批量邮件发送量之后调用 SetBatchMailCount(long preCount) 方法,MailHelper就会在(批量)同步、(批量)异步邮件全部发送完之后自动释放SmtpClient实例。 a) 为什么要“重用”同一个SmtpClient实例 因为,每次发送一封电子邮件,都必须经过TCP的三次握手与服务器建立连接,这个连接信息就保存在SmtpClient实例中,所以当进行大批量的电子邮件发送时(前提是发件地址是相同的,当然大部分场景下发件地址都是相同的),有必要重用SmtpClient实例,避免TCP不断地发生“三次握手和四次挥手”。 b) 为什么要“显示释放”SmtpClient实例 SmtpClient类没有 Finalize (终结器)方法,因此应用程序"必须"调用 Dispose 来显式释放资源。 Dispose 方法在所有建立到 Host 属性中指定的 SMTP 服务器的连接中循环,并发送 QUIT 消息,其后平稳断开 TCP 连接。
MailHelper组件的一个示例以及几种方式发邮件的优劣测试 示例(博文开始处已给出链接下载)包含四次实验方案和两组复选框,如图:
示例代码下载后注意,请先修改如Config.cs文件的几处红色标识信息(如下图),你才能正常发送邮件。
用QQ邮箱发件的注意啦,要在“设置”-“账户”中将“POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务”服务都开启才能正常发送邮件。如图:
实验一:单条邮件同步和异步发送(可通过添加大附件来观察同步异步效果) 观察:通过大附件观察下同步发送邮件和异步发送邮件的效果,查看下单封邮件发送MailHelper类是如何使用的。
实验二:批量邮件同步和异步发送(单个线程,单个SmtpClient实例,SendAsync()) 观察:观察下MailHelper类中批量异步发送使用队列方式实现的高响应性,以及批量操作如何自动释放SmtpClient实例。
在数量较大的批量邮件发送场景中,我们可以使用多个SmtpClient实例来并行发送,以提高整体发件效率。即实验三 + 实验四(不清楚并行类库的,请看 《异步编程:.NET4.X 数据并行》 )
实验三:批量邮件同步和异步发送(平行类库Parallel(自动分区),每个分区一个MailHelper、SmtpClient实例) 观察:现在通过Parallel.For的自动分区 + 每个分区一个MailHelper 和SmtpClient实例来提高整体效率。但是,有个问题就是自动分区又.NET内部根据资源负载均衡自动分区,分区的效果非常不好,总会开启过多的分区导致MailHelper和SmtpClient实例偏多,并且效率不高。
实验四:批量邮件同步和异步发送(平行类库Parallel(手动分区),每个分区一个MailHelper、SmtpClient实例) 观察:在通过Parallel.Foreach的手动分区 + 每个分区一个MailHelper 和SmtpClient实例来提高整体效率。我们自己根据业务场景和Environment.ProcessorCount内核数来决定分区数,这样可以根据需要创建MailHelper和SmtpClient实例,并且效率非常高。
另外:重用SmtpClient复选款的测试结果:如果只是简单的纯文本邮件发送(即,没有耗时的附件内容),重用SmtpClient可提升50%的效率。(注意:需要使用批量同步方式发送进行测试。因为异步方式会使用多个SmtpClient进行并行发送所以测试不出效率提升)
来个整个示例截图:
本邮件发送功能分享到此结束,如果你看后觉得对你有帮助的,还请多帮推荐……推荐……如果内容有误的,还请帮忙指出,谢谢! http://www.cnblogs.com/heyuquan/p/net-batch-mail-send-async.html |