#define debugusing System;using System.Text;using System.Linq;using System.IO;using System.Net;using System.Net.Mail;using System.Net.Mime;using System.Threading;using System.ComponentModel;using System.Diagnostics;using System.Collections.Generic;using System.Collections.Concurrent;using System.Collections.ObjectModel;namespace Mail_Test.Mail{ /// <summary> /// 非线程安全类 /// 使用注意事项: /// 1、该类无需(也不能)在外部包裹多线程,因为内部有提供“异步发送”方法,内、外都使用多线程会导致线程池对可用资源的误判,从而创建过多阻塞线程。 /// 2、MailHelper类的 m_autoDisposeSmtp 属性的使用,具体见此字段注释。 /// 3、启用 UTF-8 字符编码 /// </summary> public class MailHelper { #region 构造函数 /// <summary> /// 构建 MailHelper 实例 /// </summary> /// <param name="isAsync">是否启用异步邮件发送,默认为同步发送</param> public MailHelper(bool isAsync = false) { m_IsAsync = isAsync; } /// <summary> /// 构建 MailHelper 实例 /// </summary> /// <param name="mSmtpClient">SmtpClient实例</param> /// <param name="autoReleaseSmtp">是否自动释放SmtpClient实例</param> /// <param name="isAsync">是否启用异步邮件发送</param> public MailHelper(SmtpClient mSmtpClient, bool autoReleaseSmtp, bool isAsync = false) { this.SetSmtpClient(mSmtpClient, autoReleaseSmtp); m_IsAsync = isAsync; } #endregion #region 计划邮件数量 和 已执行完成邮件数量 // 记录和获取在大批量执行异步短信发送时已经处理了多少条记录 // 1、根据此值手动或自动释放 SmtpClient .实际上没有需要根据此值进行手动释放,因为完全可以用自动释放替换此逻辑 // 2、根据此值可以自己设置进度 private long m_CompletedSendCount = 0; public long CompletedSendCount { get { return Interlocked.Read(ref m_CompletedSendCount); } private set { Interlocked.Exchange(ref m_CompletedSendCount, value); } } // 计划邮件数量 private long m_PrepareSendCount = 0; public long PrepareSendCount { get { return Interlocked.Read(ref m_PrepareSendCount); } private set { Interlocked.Exchange(ref m_PrepareSendCount, value); } } #endregion #region 异步 发送邮件相关参数 // 是否启用异步发送邮件 private bool m_IsAsync = false; // 案例:因为异步发送邮件在SmtpClient处必须加锁保证一封一封的发送。 // 这样阻塞了主线程。所以换用队列的方式以无阻塞的方式进行异步发送大批量邮件 // 发送任务可能很长,所以使用 Thread 而不是用ThreadPool。(避免长时间暂居线程池线程),并且SmtpClient只支持一次一封邮件发送 private Thread m_SendMailThread = null; private AutoResetEvent m_AutoResetEvent = null; private AutoResetEvent AutoResetEvent { get { if (m_AutoResetEvent == null) m_AutoResetEvent = new AutoResetEvent(true); return m_AutoResetEvent; } } // 待发送队列缓存数量。单独开个计数是为了提高获取此计数的效率 private int m_messageQueueCount = 0; // 因为 MessageQueue 可能在 m_SendMailThread 线程中进行出队操作,所以使用并发队列ConcurrentQueue. // 队列中的数据只能通过取消异步发送进行清空,或则就会每一元素都执行发送邮件 private ConcurrentQueue<MailUserState> m_MessageQueue = null; private ConcurrentQueue<MailUserState> MessageQueue { get { if (m_MessageQueue == null) m_MessageQueue = new ConcurrentQueue<MailUserState>(); return m_MessageQueue; } } /// <summary> /// 在执行异步发送时传递的对象,用于传递给异步发生完成时调用的方法 OnSendCompleted 。 /// </summary> public object AsycUserState { get; set; } #endregion #region 内部字段、属性 private SmtpClient m_SmtpClient = null; /// <summary> /// 默认为false。设置在 MailHelper 类内部,发送完邮件后是否自动释放 SmtpClient 实例 /// Smtp不管是在 MailHelper 内部还是在外部都必须进行主动释放, /// 因为:SmtpClient 没有提供 Finalize() 终结器,所以GC不会进行回收,只能使用完后主动进行释放,否则会发生内存泄露问题。 /// /// 何时将 autoReleaseSmtp 设置为false,就是SmtpClient需要重复使用的情况,即需要使用“相同MailHelper”向“相同Smtp服务器”发送大批量的邮件时。 /// </summary> private bool m_autoDisposeSmtp = false; /// <summary> /// 设置此电子邮件的收件人的地址集合。 /// </summary> Dictionary<string, string> m_DicTo = null; Dictionary<string, string> DicTo { get { if (m_DicTo == null) m_DicTo = new Dictionary<string, string>(); return m_DicTo; } } /// <summary> /// 设置此电子邮件的抄送 (CC) 收件人的地址集合。 /// </summary> Dictionary<string, string> m_DicCC = null; Dictionary<string, string> DicCC { get { if (m_DicCC == null) m_DicCC = new Dictionary<string, string>(); return m_DicCC; } } /// <summary> /// 设置此电子邮件的密件抄送 (BCC) 收件人的地址集合。 /// </summary> Dictionary<string, string> m_DicBcc = null; Dictionary<string, string> DicBcc { get { if (m_DicBcc == null) m_DicBcc = new Dictionary<string, string>(); return m_DicBcc; } } // 附件集合 Collection<Attachment> m_Attachments; Collection<Attachment> Attachments { get { if (m_Attachments == null) m_Attachments = new Collection<Attachment>(); return m_Attachments; } } // 指定一个电子邮件不同格式显示的副本。 Collection<AlternateView> m_AlternateViews; Collection<AlternateView> AlternateViews { get { if (m_AlternateViews == null) m_AlternateViews = new Collection<AlternateView>(); return m_AlternateViews; } } #endregion #region 公开属性 /// <summary> /// 设置此电子邮件的发信人地址。 /// </summary> public string From { get; set; } /// <summary> /// 设置此电子邮件的发信人地址。 /// </summary> public string FromDisplayName { get; set; } /// <summary> /// 设置此电子邮件的主题。 /// </summary> public string Subject { get; set; } /// <summary> /// 设置邮件正文。 /// </summary> public string Body { get; set; } /// <summary> /// 设置邮件正文是否为 Html 格式的值。 /// </summary> public bool IsBodyHtml { get; set; } private int priority = 0; /// <summary> /// 设置此电子邮件的优先级 0-Normal 1-Low 2-High /// 默认Normal。 /// </summary> public int Priority { get { return this.priority; } set { if (value < 0 || value > 2) priority = 0; else priority = value; } } #endregion /// <summary> /// 重置 MailHelper 实例信息 /// 不释放 SmtpClient 实例和相关的AutoReleaseSimple字段,因为存在异步发送。。这两个字段由SetSmtpClient方法设置 /// </summary> public void Reset() { From = String.Empty; FromDisplayName = String.Empty; if (m_DicTo != null) m_DicTo.Clear(); if (m_DicCC != null) m_DicCC.Clear(); if (m_DicBcc != null) m_DicBcc.Clear(); if (m_Attachments != null) m_Attachments.Clear(); if (m_AlternateViews != null) m_AlternateViews.Clear(); Subject = String.Empty; Body = String.Empty; IsBodyHtml = false; priority = 0; AsycUserState = null; // 1、不重置SmtpClient。根据 m_autoDisposeSmtp 参数自动释放或由外部主动释放 // 2、不重置:异步待发送队列及队列计数,AutoResetEvent实例,执行异步发送线程,是否启用异步发送标识 } #region SmtpClient 相关方法 /// <summary> /// 检查此 MailHelper 实例是否已经设置了 SmtpClient /// </summary> /// <returns>true代表已设置</returns> public bool ExistsSmtpClient() { return m_SmtpClient != null ? true : false; } /// <summary> /// 设置 SmtpClient 实例 和是否自动释放Smtp的唯一入口 /// 1、将内部 计划数量 和 已完成数量 清零,重新统计以便自动释放SmtpClient /// 2、若要对SmtpClent设置SendCompleted事件,请在调用此方法前进行设置 /// </summary> /// <param name="mSmtpClient"> SmtpClient 实例</param> /// <param name="autoReleaseSmtp">设置在 MailHelper 类内部,发送完邮件后是否自动释放 SmtpClient 实例</param> public void SetSmtpClient(SmtpClient mSmtpClient, bool autoReleaseSmtp) {#if DEBUG Debug.WriteLine("设置SmtpClient,自动释放为" (autoReleaseSmtp ? "TRUE" : "FALSE"));#endif m_SmtpClient = mSmtpClient; m_autoDisposeSmtp = autoReleaseSmtp; // 将内部 计划数量 和 已完成数量 清零,重新统计以便自动释放SmtpClient (MailHelper实例唯一的清零地方) m_PrepareSendCount = 0; m_CompletedSendCount = 0; if (m_IsAsync && autoReleaseSmtp) { // 注册内部释放回调事件.释放对象---该事件不进行取消注册,只在释放SmtpClient时,一起释放 (所以SmtpClient与MailHelper绑定后,就不要再单独使用了) m_SmtpClient.SendCompleted = new SendCompletedEventHandler(SendCompleted4Dispose); } } /// <summary> /// 释放 SmtpClient /// </summary> public void ManualDisposeSmtp() { this.InnerDisposeSmtp(); } /// <summary> /// 释放SmtpClient /// </summary> private void AutoDisposeSmtp() { if (m_autoDisposeSmtp && m_SmtpClient != null) { if (PrepareSendCount == 0) { // PrepareSendCount=0 说明还未设置计划批量邮件数,所以不自动释放SmtpClient。 // 不能因为小于CompletedSendCount就报错,因为可能是先发送再设置计划邮件数量 } else if (PrepareSendCount < CompletedSendCount) { throw new Exception(MailValidatorHelper.EMAIL_ADDRESS_RANGE_ERROR); } else if (PrepareSendCount == CompletedSendCount) { this.InnerDisposeSmtp(); } } else { // 不清空和Dispose()内部的SmtpClient字段,即用在需要重复使用时不需要再调用 SetSmtpClient() 进行设置。 } } /// <summary> /// 释放SmtpClient /// </summary> private void InnerDisposeSmtp() { if (m_SmtpClient != null) {#if DEBUG Debug.WriteLine("释放SMtpClient");#endif m_SmtpClient.Dispose(); m_SmtpClient = null; // 在设置 SmtpClient 入口处重新进行设置 m_autoDisposeSmtp = false; PrepareSendCount = 0; CompletedSendCount = 0; } } #endregion #region MessageAddress、Attachment、AlternateView 相关方法 #region 添加收件人、抄送人、密送人(每个类型中,若地址有重复,只保留第一个地址) /// <summary> /// 添加收件人、抄送人、密送人(每个类型中,若地址有重复,只保留第一个地址) /// </summary> /// <param name="type">类型:收件人、抄送人、密送人</param> /// <param name="addressList">Email地址列表</param> public void AddReceive(EmailAddrType type, IEnumerable<string> addressList) { MailValidatorHelper.ValideArgumentNull<IEnumerable<string>>(addressList, "addressList"); if (addressList.Count() > 0) { Dictionary<string, string> dic = null; switch (type) { case EmailAddrType.To: dic = DicTo; break; case EmailAddrType.CC: dic = DicCC; break; case EmailAddrType.Bcc: dic = DicBcc; break; case EmailAddrType.From: throw new Exception(MailValidatorHelper.EMAIL_ADDRESS_RANGE_ERROR); } foreach (string address in addressList) { MailValidatorHelper.ValideStrNullOrEmpty(address, "addressList", MailValidatorHelper.EMAIL_ADDRESS_LIST_ERROR); if (dic.Count > 0 && !dic.ContainsKey(address)) dic.Add(address, String.Empty); } } } /// <summary> /// 添加收件人、抄送人、密送人(每个类型中,若地址有重复,只保留第一个地址) /// </summary> /// <param name="type">类型:收件人、抄送人、密送人</param> /// <param name="address">Email地址</param> /// <param name="displayName">显示名称</param> public void AddReceive(EmailAddrType type, string address, string displayName) { MailValidatorHelper.ValideStrNullOrEmpty(address, "address"); Dictionary<string, string> dic = null; switch (type) { case EmailAddrType.To: dic = DicTo; break; case EmailAddrType.CC: dic = DicCC; break; case EmailAddrType.Bcc: dic = DicBcc; break; case EmailAddrType.From: throw new Exception(MailValidatorHelper.EMAIL_ADDRESS_RANGE_ERROR); } if (dic.Count == 0 || !dic.ContainsKey(address)) dic.Add(address, displayName); } /// <summary> /// 添加收件人、抄送人、密送人(每个类型中,若地址有重复,只保留第一个地址) /// </summary> /// <param name="type">类型:收件人、抄送人、密送人</param> /// <param name="dicAddress">Email地址,显示名称</param> public void AddReceive(EmailAddrType type, Dictionary<string, string> dicAddress) { MailValidatorHelper.ValideArgumentNull<Dictionary<string, string>>(dicAddress, "dicAddress"); if (dicAddress.Count > 0) { Dictionary<string, string> dic = null; switch (type) { case EmailAddrType.To: dic = DicTo; break; case EmailAddrType.CC: dic = DicCC; break; case EmailAddrType.Bcc: dic = DicBcc; break; case EmailAddrType.From: throw new Exception(MailValidatorHelper.EMAIL_ADDRESS_RANGE_ERROR); } foreach (KeyValuePair<string, string> keyValue in dicAddress) { MailValidatorHelper.ValideStrNullOrEmpty(keyValue.Key, "dicAddress", MailValidatorHelper.EMAIL_ADDRESS_DIC_ERROR); if (dic.Count > 0 && !dic.ContainsKey(keyValue.Key)) dic.Add(keyValue.Key, keyValue.Value); } } } #endregion #region 添加附件 /// <summary> /// 添加单个附件 /// </summary> /// <param name="attachment">Attachment附件实例</param> public void AddAttachment(Attachment attachment) { MailValidatorHelper.ValideArgumentNull<Attachment>(attachment, "attachment"); Attachments.Add(attachment); } /// <summary> /// 添加单个附件 /// </summary> /// <param name="fieldPath">待上传文件路径</param> /// <param name="fileName">文件显示名称(不带后缀)</param> public void AddAttachment(string fieldPath, string fileName = "") { MailValidatorHelper.ValideStrNullOrEmpty(fieldPath, "fieldPath"); this.InnerAddAttachment(fieldPath, fileName, false, String.Empty); } /// <summary> /// 添加内嵌资源(eg:图片,mp3等等) /// </summary> /// <param name="fieldPath">内嵌资源的文件路径</param> /// <param name="cidName">设置此附件的 MIME 内容 ID</param> public void AddInlineAttachment(string fieldPath, string cidName) { MailValidatorHelper.ValideStrNullOrEmpty(fieldPath, "fieldPath"); MailValidatorHelper.ValideStrNullOrEmpty(cidName, "cidName"); this.InnerAddAttachment(fieldPath, String.Empty, true, cidName); } private void InnerAddAttachment(string fieldPath, string fileName, bool isInline, string cidName) { // 因为Attachment中存储的时FilePath对应文件的Stream,所以这边在获取FileInfo信息的时候,同时转化为Stream传递给Attachment实例, // 避免再次根据FilePath获取文件内容 FileInfo file = new FileInfo(fieldPath); Stream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read); Attachment data = new Attachment(stream, String.Empty); //实例邮件内容 ContentDisposition disposition = data.ContentDisposition; if (isInline) { disposition.Inline = true; // 设置此附件的 MIME 内容 ID。 data.ContentId = cidName; } // 设置文件附件的创建日期。 disposition.CreationDate = file.CreationTime; // 设置文件附件的修改日期。 disposition.ModificationDate = file.LastWriteTime; // 设置文件附件的读取日期。 disposition.ReadDate = file.LastAccessTime; // 设定文件名称 (内嵌资源设置文件名后下载下来才有默认后缀) if (String.IsNullOrEmpty(fileName)) disposition.FileName = file.Name.ToString(); else { disposition.FileName = fileName Path.GetExtension(fieldPath); } Attachments.Add(data); } #endregion #region 添加AlternateView // 指定一个电子邮件不同格式的副本。 //(eg:发送HTML格式的邮件,可能希望同时提供邮件的纯文本格式,以防止一些收件人使用的电子邮件阅读程序无法显示html内容) /// <summary> /// 添加一个电子邮件不同格式的副本。 /// </summary> /// <param name="filePath">包含电子邮件内容的文件路径</param> public void AddAlterViewPath(string filePath) { MailValidatorHelper.ValideStrNullOrEmpty(filePath, "filePath"); AlternateViews.Add(new AlternateView(filePath)); } /// <summary> /// 添加一个电子邮件不同格式的副本。 /// </summary> /// <param name="mailContent">电子邮件内容</param> public void AddAlterViewContent(string mailContent) { MailValidatorHelper.ValideStrNullOrEmpty(mailContent, "mailContent"); AlternateViews.Add(AlternateView.CreateAlternateViewFromString(mailContent)); } /// <summary> /// 添加一个电子邮件不同格式的副本。 /// </summary> /// <param name="contentStream">电子邮件内容流</param> public void AddAlterViewStream(Stream contentStream) { MailValidatorHelper.ValideArgumentNull<Stream>(contentStream, "contentStream"); AlternateViews.Add(new AlternateView(contentStream)); } /// <summary> /// 添加一个电子邮件不同格式的副本。 /// </summary> /// <param name="alternateView">电子邮件视图</param> public void AddAlternateView(AlternateView alternateView) { MailValidatorHelper.ValideArgumentNull<AlternateView>(alternateView, "alternateView"); AlternateViews.Add(alternateView); } #endregion #endregion #region 发送邮件 相关方法 /// <summary> /// 计划批量发送邮件的个数,配合自动释放SmtpClient。(批量邮件发送不调用此方法就不会自动释放SmtpClient) /// 0、此方法可以在发送邮件方法之前或之后调用 /// 1、只有设置后才会自动根据 m_autoDisposeSmtp 字段进行释放SmtpClient。 /// 2、若 m_autoDisposeSmtp = false 即由自己手动进行设置的无需调用此方法设置预计邮件数 /// </summary> /// <param name="preCount">计划邮件数量</param> public void SetBatchMailCount(long preCount) { PrepareSendCount = preCount; if (preCount < CompletedSendCount) { throw new ArgumentOutOfRangeException("preCount", MailValidatorHelper.EMAIL_ADDRESS_RANGE_ERROR); } else if (preCount == CompletedSendCount) { if (m_autoDisposeSmtp) this.InnerDisposeSmtp(); } } /// <summary> /// 同步发送一封Email /// </summary> public void SendOneMail() { m_PrepareSendCount = 1; this.InnerSendMessage(); } /// <summary> /// 批量同步发送Email /// </summary> public void SendBatchMail() { this.InnerSendMessage(); } /// <summary> /// 取消异步邮件发送 /// </summary> public void SendAsyncCancel() { // 因为此类为非线程安全类,所以 SendAsyncCancel 和发送邮件方法中操作MessageQueue部分的代码肯定是串行化的。 // 所以不存在一边入队,一边出队导致无法完全取消所有邮件发送 // 1、清空队列。 // 2、取消正在异步发送的mail。 // 3、设置计划数量=完成数量 // 4、执行 AutoDisposeSmtp() if (m_IsAsync) { // 1、清空队列。 MailUserState tempMailUserState = null; while (MessageQueue.TryDequeue(out tempMailUserState)) { Interlocked.Decrement(ref m_messageQueueCount); MailMessage message = tempMailUserState.CurMailMessage; this.InnerDisposeMessage(message); } tempMailUserState = null; // 2、取消正在异步发送的mail。 m_SmtpClient.SendAsyncCancel(); // 3、设置计划数量=完成数量 PrepareSendCount = CompletedSendCount; // 4、执行 AutoDisposeSmtp() this.AutoDisposeSmtp(); } else { throw new Exception(MailValidatorHelper.EMAIL_ASYNC_CALL_ERROR); } } /// <summary> /// 发送Email /// </summary> private void InnerSendMessage() { bool hasError = false; MailMessage mMailMessage = null; #region 构建 MailMessage try { mMailMessage = new MailMessage(); mMailMessage.From = new MailAddress(From, FromDisplayName); this.InnerSetAddress(EmailAddrType.To, mMailMessage); this.InnerSetAddress(EmailAddrType.CC, mMailMessage); this.InnerSetAddress(EmailAddrType.Bcc, mMailMessage); mMailMessage.Subject = Subject; mMailMessage.Body = Body; if (m_Attachments != null && m_Attachments.Count > 0) { foreach (Attachment attachment in m_Attachments) mMailMessage.Attachments.Add(attachment); } mMailMessage.SubjectEncoding = Encoding.UTF8; mMailMessage.BodyEncoding = Encoding.UTF8; // SmtpClient 的 Headers 中会根据 MailMessage 默认设置些值,所以应该为 UTF8 。 mMailMessage.HeadersEncoding = Encoding.UTF8; mMailMessage.IsBodyHtml = IsBodyHtml; if (m_AlternateViews != null && m_AlternateViews.Count > 0) { foreach (AlternateView alternateView in AlternateViews) { mMailMessage.AlternateViews.Add(alternateView); } } mMailMessage.Priority = (MailPriority)Priority; } catch (ArgumentNullException argumentNullEx) { hasError = true; throw argumentNullEx; } catch (ArgumentException argumentEx) { hasError = true; throw argumentEx; } catch (FormatException formatEx) { hasError = true; throw formatEx; } finally { if (hasError) { if (mMailMessage != null) { this.InnerDisposeMessage(mMailMessage); mMailMessage = null; } this.InnerDisposeSmtp(); } } #endregion if (!hasError) { if (m_IsAsync) { #region 异步发送邮件 if (PrepareSendCount == 1) { // 情况一:不重用 SmtpClient 实例会将PrepareSendCount设置为1 // 情况二:计划发送只有一条 // PrepareSendCount 是发送单条邮件。 MailUserState state = new MailUserState() { AutoReleaseSmtp = m_autoDisposeSmtp, CurMailMessage = mMailMessage, CurSmtpClient = m_SmtpClient, IsSmpleMail = true, UserState = AsycUserState, }; if (m_autoDisposeSmtp) // 由发送完成回调函数根据 IsSmpleMail 字段进行释放 m_SmtpClient = null; ThreadPool.QueueUserWorkItem((userState) => { // 无需 catch 发送异常,因为是异步,所以这里 catch 不到。 MailUserState curUserState = userState as MailUserState; curUserState.CurSmtpClient.SendAsync(mMailMessage, userState); }, state); } else { // 情况一:重用 SmtpClient 逻辑,即我们可以直接操作全局的 m_SmtpClient // 情况二:批量发送邮件 PrepareSendCount>1 // 情况三:PrepareSendCount 还未设置,为0。比如场景在循环中做些判断,再决定发邮件,循环完才调用 SetBatchMailCount 设置计划邮件数量 MailUserState state = new MailUserState() { AutoReleaseSmtp = m_autoDisposeSmtp, CurMailMessage = mMailMessage, CurSmtpClient = m_SmtpClient, UserState = AsycUserState, }; MessageQueue.Enqueue(state); Interlocked.Increment(ref m_messageQueueCount); if (m_SendMailThread == null) { m_SendMailThread = new Thread(() => { // noItemCount 次获取不到元素,就抛出线程异常 int noItemCount = 0; while (true) { if (PrepareSendCount != 0 && PrepareSendCount == CompletedSendCount) { // 已执行完毕。 this.AutoDisposeSmtp(); break; } else { MailUserState curUserState = null; if (!MessageQueue.IsEmpty) {#if DEBUG Debug.WriteLine("WaitOne" Thread.CurrentThread.ManagedThreadId);#endif // 当执行异步取消时,会清空MessageQueue,所以 WaitOne 必须在从MessageQueue中取到元素之前 AutoResetEvent.WaitOne(); if (MessageQueue.TryDequeue(out curUserState)) { Interlocked.Decrement(ref m_messageQueueCount); m_SmtpClient.SendAsync(curUserState.CurMailMessage, curUserState); } } else { if (noItemCount >= 10) { // 没有正确设置 PrepareSendCount 值。导致已没有邮件但此线程出现死循环 this.InnerDisposeSmtp(); throw new Exception(MailValidatorHelper.EMAIL_PREPARESENDCOUNT_NOTSET_ERROR); } Thread.Sleep(1000); noItemCount ; } } // SmtpClient 为null表示异步预计发送邮件数已经发送完,在 OnSendCompleted 进行了 m_SmtpClient 释放 if (m_SmtpClient == null) break; } m_SendMailThread = null; }); m_SendMailThread.Start(); } } #endregion } else { #region 同步发送邮件 try { m_SmtpClient.Send(mMailMessage); m_CompletedSendCount ; } catch (ObjectDisposedException smtpDisposedEx) { throw smtpDisposedEx; } catch (InvalidOperationException smtpOperationEx) { throw smtpOperationEx; } catch (SmtpFailedRecipientsException smtpFailedRecipientsEx) { throw smtpFailedRecipientsEx; } catch (SmtpException smtpEx) { throw smtpEx; } finally { if (mMailMessage != null) { this.InnerDisposeMessage(mMailMessage); mMailMessage = null; } this.AutoDisposeSmtp(); } #endregion } } } /// <summary> /// 将收件人、抄送人、密送人添加到 MailMessage 中 /// </summary> /// <param name="type">收件人、抄送人、密送人</param> /// <param name="mMailMessage">待发送的MailMessage类</param> private void InnerSetAddress(EmailAddrType type, MailMessage mMailMessage) { MailAddressCollection receiveCol = null; Dictionary<string, string> dicReceive = null; bool hasAddress = false; switch (type) { case EmailAddrType.To: { if (m_DicTo != null && m_DicTo.Count > 0) { dicReceive = m_DicTo; receiveCol = mMailMessage.To; hasAddress = true; } } break; case EmailAddrType.CC: { if (m_DicCC != null && m_DicCC.Count > 0) { dicReceive = m_DicCC; receiveCol = mMailMessage.CC; hasAddress = true; } } break; case EmailAddrType.Bcc: { if (m_DicBcc != null && m_DicBcc.Count > 0) { dicReceive = m_DicBcc; receiveCol = mMailMessage.Bcc; hasAddress = true; } } break; case EmailAddrType.From: throw new Exception(MailValidatorHelper.EMAIL_ADDRESS_RANGE_ERROR); } if (hasAddress) { foreach (KeyValuePair<string, string> keyValue in dicReceive) { receiveCol.Add(new MailAddress(keyValue.Key, keyValue.Value)); } } } /// <summary> /// 释放 MailMessage 对象 /// </summary> private void InnerDisposeMessage(MailMessage message) { if (message != null) { if (message.AlternateViews.Count > 0) { message.AlternateViews.Dispose(); } message.Dispose(); message = null; } } /// <summary> /// 声明在 SmtpClient.SendAsync() 执行完后释放相关对象的回调方法 最后触发的委托 /// </summary> protected void SendCompleted4Dispose(object sender, AsyncCompletedEventArgs e) { MailUserState state = e.UserState as MailUserState; if (state.CurMailMessage != null) { MailMessage message = state.CurMailMessage; this.InnerDisposeMessage(message); state.CurMailMessage = null; } if (state.IsSmpleMail) { if (state.AutoReleaseSmtp && state.CurSmtpClient != null) {#if DEBUG Debug.WriteLine("释放SmtpClient");#endif state.CurSmtpClient.Dispose(); state.CurSmtpClient = null; } } else { if (!e.Cancelled) // 取消的就不计数 CompletedSendCount ; if (state.AutoReleaseSmtp) { this.AutoDisposeSmtp(); } // 若批量异步发送,需要设置信号#if DEBUG Debug.WriteLine("Set" Thread.CurrentThread.ManagedThreadId);#endif AutoResetEvent.Set(); } // 先释放资源,处理错误逻辑 if (e.Error != null && !state.IsErrorHandle) { throw e.Error; } } #endregion #region 异步发送邮件,MessageQueue队列中缓冲的待发邮件数量,使用者可根据此数量来限制邮件数量,以免内存浪费 /// <summary> /// 获取异步发送邮件,MessageQueue队列中缓冲的待发邮件数量 /// (使用者可根据此数量来限制邮件数量,以免内存浪费) /// </summary> public int GetAwaitMailCountAsync() { if (m_IsAsync) { return Thread.VolatileRead(ref m_messageQueueCount); } else { throw new Exception(MailValidatorHelper.EMAIL_ASYNC_CALL_ERROR); } } #endregion #region 发送邮件前检查 相关方法 /// <summary> /// 发送邮件前检查需要设置的信息是否完整,收集(提示 错误)信息 /// </summary> public Dictionary<MailInfoType, string> CheckSendMail() { Dictionary<MailInfoType, string> dicMsg = new Dictionary<MailInfoType, string>(); this.InnerCheckSendMail4Info(dicMsg); this.InnerCheckSendMail4Error(dicMsg); return dicMsg; } /// <summary> /// 发送邮件前检查需要设置的信息是否完整,收集 提示 信息 /// </summary> public Dictionary<MailInfoType, string> CheckSendMail4Info() { Dictionary<MailInfoType, string> dicMsg = new Dictionary<MailInfoType, string>(); this.InnerCheckSendMail4Info(dicMsg); return dicMsg; } /// <summary> /// 发送邮件前检查需要设置的信息是否完整,收集 错误 信息 /// </summary> public Dictionary<MailInfoType, string> CheckSendMail4Error() { Dictionary<MailInfoType, string> dicMsg = new Dictionary<MailInfoType, string>(); this.InnerCheckSendMail4Error(dicMsg); return dicMsg; } /// <summary> /// 发送邮件前检查需要设置的信息是否完整,收集 提示 信息 /// </summary> /// <param name="dicMsg">将检查信息收集到此集合</param> private void InnerCheckSendMail4Info(Dictionary<MailInfoType, string> dicMsg) { // 注意每个验证使用完 infoBuilder 都要清零 infoBuilder 。 StringBuilder infoBuilder = new StringBuilder(128); this.InnerCheckAddress(infoBuilder, dicMsg, EmailAddrType.CC); this.InnerCheckAddress(infoBuilder, dicMsg, EmailAddrType.Bcc); // 邮件主题 if (Subject.Length == 0) dicMsg.Add(MailInfoType.SubjectEmpty, MailInfoHelper.GetMailInfoStr(MailInfoType.SubjectEmpty)); // 邮件内容 if (Body.Length == 0 && (m_Attachments == null || (m_Attachments != null && m_Attachments.Count == 0)) ) { dicMsg.Add(MailInfoType.BodyEmpty, MailInfoHelper.GetMailInfoStr(MailInfoType.BodyEmpty)); } } /// <summary> /// 发送邮件前检查需要设置的信息是否完整,收集 错误 信息 /// </summary> /// <param name="dicMsg">将检查信息收集到此集合</param> private void InnerCheckSendMail4Error(Dictionary<MailInfoType, string> dicMsg) { // 注意每个验证使用完 infoBuilder 都要清零 infoBuilder 。 StringBuilder infoBuilder = new StringBuilder(128); this.InnerCheckAddress(infoBuilder, dicMsg, EmailAddrType.From); this.InnerCheckAddress(infoBuilder, dicMsg, EmailAddrType.To); // SmtpClient 实例未设置 if (m_SmtpClient == null) dicMsg.Add(MailInfoType.SmtpClientEmpty, MailInfoHelper.GetMailInfoStr(MailInfoType.SmtpClientEmpty)); else { // SMTP 主服务器设置 (默认端口为25) if (m_SmtpClient.Host.Length == 0) dicMsg.Add(MailInfoType.HostEmpty, MailInfoHelper.GetMailInfoStr(MailInfoType.HostEmpty)); // SMPT 凭证 if (m_SmtpClient.EnableSsl && m_SmtpClient.ClientCertificates.Count == 0) dicMsg.Add(MailInfoType.CertificateEmpty, MailInfoHelper.GetMailInfoStr(MailInfoType.CertificateEmpty)); } } /// <summary> /// 检查 发件人、收件人、抄送人、密送人 邮箱地址 /// </summary> /// <param name="infoBuilder">StringBuilder实例</param> /// <param name="dicMsg">将检查信息收集到此集合</param> /// <param name="type">接收邮件地址类型</param> private void InnerCheckAddress(StringBuilder infoBuilder, Dictionary<MailInfoType, string> dicMsg, EmailAddrType type) { Dictionary<string, string> dic = null; MailInfoType addressFormat = MailInfoType.None; MailInfoType addressEmpty = MailInfoType.None; bool allowEmpty = true; // 只有 发件人 是单个地址,特别进行处理 bool hasHandle = false; switch (type) { case EmailAddrType.From: { // 标识为已处理 hasHandle = true; allowEmpty = false; if (From.Length == 0) { dicMsg.Add(MailInfoType.FromEmpty, MailInfoHelper.GetMailInfoStr(MailInfoType.FromEmpty)); } else if (!MailValidatorHelper.IsEmail(From)) { string strTemp = infoBuilder.AppendFormat(MailInfoHelper.GetMailInfoStr(MailInfoType.FromFormat), FromDisplayName, From).ToString(); dicMsg.Add(MailInfoType.FromFormat, strTemp); infoBuilder.Length = 0; } } break; case EmailAddrType.To: { dic = m_DicTo; addressEmpty = MailInfoType.ToEmpty; allowEmpty = false; addressFormat = MailInfoType.ToFormat; } break; case EmailAddrType.CC: { dic = m_DicCC; addressFormat = MailInfoType.CCFormat; allowEmpty = true; addressEmpty = MailInfoType.None; } break; case EmailAddrType.Bcc: { dic = m_DicBcc; addressFormat = MailInfoType.BccFormat; allowEmpty = true; addressEmpty = MailInfoType.None; } break; } #region 处理 收件人、抄送人、密送人 if (!hasHandle) { if (dic == null) { if (!allowEmpty) { // 地址为空 dicMsg.Add(addressEmpty, MailInfoHelper.GetMailInfoStr(addressEmpty)); } } else { if (dic.Count > 0) { string strTemp = String.Empty; // 邮件地址格式 foreach (KeyValuePair<string, string> keyValue in dic) { if (keyValue.Key.Length == 0) { if (!allowEmpty) { // 地址为空 dicMsg.Add(addressEmpty, MailInfoHelper.GetMailInfoStr(addressEmpty)); } } else if (!MailValidatorHelper.IsEmail(keyValue.Key)) { if (strTemp.Length == 0) strTemp = MailInfoHelper.GetMailInfoStr(addressFormat); if (infoBuilder.Length > 0) infoBuilder.AppendLine(); infoBuilder.AppendFormat(strTemp, keyValue.Value, keyValue.Key); } } if (infoBuilder.Length > 0) { dicMsg.Add(addressFormat, infoBuilder.ToString()); infoBuilder.Length = 0; } } else if (!allowEmpty) { // 地址为空 dicMsg.Add(addressEmpty, MailInfoHelper.GetMailInfoStr(addressEmpty)); } } } #endregion } #endregion } /// <summary> /// 异步发送邮件时保存的信息,用于释放和传递数据 /// </summary> public class MailUserState { #region 由MailHelper内部的SendCompleted注册的事件使用 // 用于释放 MailMessage 和 SmtpClient public MailMessage CurMailMessage { get; set; } public bool AutoReleaseSmtp { get; set; } public SmtpClient CurSmtpClient { get; set; } // 只发送单封邮件的时候使用此进行判断释放 public bool IsSmpleMail { get; set; } #endregion /// <summary> /// 用户传递的状态对象 /// </summary> public object UserState { get; set; } /// <summary> /// 当异步发送报错时可通过此标识是否已经处理该异常 /// </summary> public bool IsErrorHandle { get; set; } }}
下载c# 异步批量发送邮件用户还喜欢
- 18480 文章数
- 500万+ 热度
作者专栏
编辑推荐
- 淡抹u2引擎,修复内容较多,物有所值
- 界域传说·经典巨作=传世单机(一键安装)
- 丸子版本(175个传世版本大集合)
- GS版本:神话公益服务端+客户端
- 图片放大工具(放大图片不模糊)
- 剪映无限制VIP版
- 传奇世界客户端下载器,史上最全传世客户端
- 传世GS20220920商业引擎注册+登录配置器 解压密码是1
- U2官方排行榜游戏网关 支持元神,支持传家宝
- GS开战传世客户端+服务端
- (淡漠夕阳)u2引擎合区工具
- 传世GS引擎消除“你的游戏客户端版本号过旧,请及时更新”提示
- 传世一机多区双线路配置器--免密码版本
- 传世凤凰登陆器劫持修复软件
- SQLite3 for Navicat
- 传奇世界npc对话框编辑工具
- 传世GS落霞铭文服务器端
- gs_20210409引擎包+注册机(无限制)
- 传奇世界NPC对话封包查看器[支持时长版和极速版]
- 彩虹引擎传世脚本编辑工具1.7版来了,支持函数脚本翻译
评论