C#使用委托实现异步发送邮件及回调处理
在C#项目开发中,同步发送邮件常导致主线程阻塞,影响系统响应速度。本文介绍利用 System.Net.Mail 命名空间下的 SmtpClient.SendAsync 方法,配合 Action<string> 委托回调机制,实现非阻塞式邮件发送。该方案将邮件发送组件与业务日志记录分离,确保异步任务结束后自动触发后续操作。
问题背景与解决方案
在重构旧项目时,发现原有的邮件发送功能采用同步模式(Send),导致在等待邮件发送期间程序无法执行其他逻辑。由于邮件发送后的结果通常仅需写入日志,对核心业务流无直接影响,因此最适合的做法是将其改为异步执行。
难点在于:邮件发送类库通常独立于主业务模块,无法直接在主程序中插入日志代码。解决方案是利用C#的委托特性,在发送邮件的方法中接受一个回调函数作为参数。当异步发送完成(无论成功或失败)时,自动调用该回调函数来处理日志记录或其他收尾工作。
环境准备
- 开发语言: C# (.NET Framework 或 .NET Core/5+)
- 关键类库:
System.Net.Mail(用于构建邮件和客户端),System.Threading(用于延时模拟) - 前置条件: 需要有效的SMTP服务器地址、账号、密码及接收者邮箱地址
第1步:定义邮件辅助类 MailHelper
目的:封装SMTP客户端配置与邮件消息体构建,提供统一的异步发送接口。
动作:
- 定义属性:配置
SmtpServer,UserName,Pwd,Subject,Body等SMTP连接参数与邮件内容。 - 构建 MailMessage:通过
GetClient属性解析收件人列表(支持逗号分隔多个地址),设置发件人、主题、正文编码(UTF8)及优先级。 - 构建 SmtpClient:通过
GetSmtpClient属性实例化客户端,配置主机、端口、凭据及SSL启用状态。
第2步:实现 SendAsync 异步发送逻辑
目的:启动异步邮件发送进程,并注册完成时的回调处理。
入口:public void SendAsync(string subject, string body, string to, Action<string> _actinCompletedCallback)
重要参数:
to: 接收者邮箱,多个可用逗号分隔。_actinCompletedCallback: 发送完成后执行的回调函数,接收字符串类型的结果描述。
操作细节:
- 初始化
SmtpClient和MailMessage对象。 - 绑定
SendCompleted事件到内部处理方法SendCompletedCallback。 - 调用
smtpClient.SendAsync(mailMessage, "true")发起异步请求。第二个参数userToken传为 "true",用于在回调中标识请求来源。 - 捕获异常抛出,并在 Finally 块中释放本地引用。
第3步:处理异步完成回调 SendCompletedCallback
目的:根据异步操作的结果状态,决定调用何种业务回调。
动作:
- 检查取消或错误:若
e.Cancelled为真,返回“异步操作取消”;若e.Error不为空,格式化错误信息。 - 成功处理:若无错误或取消,直接使用
UserState(即传入的 "true")作为成功标识。 - 执行委托:调用之前注册的
actionSendCompletedCallback(message),将结果状态传递给外部业务逻辑(如写入日志)。
using System;
using System.Net.Mail;
namespace SendAsyncEmailTest
{
class Program
{
const string dateFormat = "yyyy-MM-dd :HH:mm:ss:ffffff";
static void Main(string[] args)
{
Console.WriteLine("开始异步发送邮件,时间:" + DateTime.Now.ToString(dateFormat));
// 调用异步发送方法,传入自定义的完成回调函数 emailCompleted
new MailHelper().SendAsync("Send Async Email Test", "This is Send Async Email Test", "recipient@example.com", emailCompleted);
Console.WriteLine("邮件正在异步发送,时间:" + DateTime.Now.ToString(dateFormat));
Console.ReadKey();
Console.WriteLine();
}
/// <summary>
/// 邮件发送后的回调方法
/// </summary>
static void emailCompleted(string message)
{
// 模拟日志写入前的处理延时
System.Threading.Thread.Sleep(1000);
Console.WriteLine();
Console.WriteLine("邮件发送结果:\r\n" + (message == "true" ? "邮件发送成功" : "邮件发送失败") + ",时间:" + DateTime.Now.ToString(dateFormat));
// 在此处添加具体的日志写入逻辑
}
}
public class MailHelper
{
private readonly int smtpPort = 25;
readonly string SmtpServer = "smtp.example.com"; // 请替换为实际SMTP服务器
private readonly string UserName = "support@example.com"; // 请替换为用户名
readonly string Pwd = "your_password"; // 请替换为密码
private readonly string AuthorName = "Author";
public string Subject { get; set; }
public string Body { get; set; }
public string Tos { get; set; }
public bool EnableSsl { get; set; }
MailMessage GetClient
{
get
{
if (string.IsNullOrEmpty(Tos)) return null;
MailMessage mailMessage = new MailMessage();
foreach (string _str in Tos.Split(','))
{
mailMessage.To.Add(_str);
}
mailMessage.From = new System.Net.Mail.MailAddress(UserName, AuthorName);
mailMessage.Subject = Subject;
mailMessage.Body = Body;
mailMessage.IsBodyHtml = true;
mailMessage.BodyEncoding = System.Text.Encoding.UTF8;
mailMessage.SubjectEncoding = System.Text.Encoding.UTF8;
mailMessage.Priority = System.Net.Mail.MailPriority.High;
return mailMessage;
}
}
SmtpClient GetSmtpClient
{
get
{
return new SmtpClient
{
UseDefaultCredentials = false,
Credentials = new System.Net.NetworkCredential(UserName, Pwd),
DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.Network,
Host = SmtpServer,
Port = smtpPort,
EnableSsl = EnableSsl,
};
}
}
// 存储回调委托
Action<string> actionSendCompletedCallback = null;
/// <summary>
/// 使用异步发送邮件
/// </summary>
/// <param name="subject">主题</param>
/// <param name="body">内容</param>
/// <param name="to">接收者,以,分隔多个接收者</param>
/// <param name="_actinCompletedCallback">邮件发送后的回调方法</param>
public void SendAsync(string subject, string body, string to, Action<string> _actinCompletedCallback)
{
if (string.IsNullOrEmpty(to)) return;
Tos = to;
SmtpClient smtpClient = GetSmtpClient;
MailMessage mailMessage = GetClient;
if (smtpClient == null || mailMessage == null) return;
Subject = subject;
Body = body;
EnableSsl = false;
// 保存回调引用
actionSendCompletedCallback = _actinCompletedCallback;
smtpClient.SendCompleted += new SendCompletedEventHandler(SendCompletedCallback);
try
{
smtpClient.SendAsync(mailMessage, "true");
}
catch (Exception e)
{
throw new Exception(e.Message);
}
finally
{
smtpClient = null;
mailMessage = null;
}
}
/// <summary>
/// 异步操作完成后执行回调方法
/// </summary>
private void SendCompletedCallback(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
if (actionSendCompletedCallback == null) return;
string message = string.Empty;
if (e.Cancelled)
{
message = "异步操作取消";
}
else if (e.Error != null)
{
message = (string.Format("UserState:{0},Message:{1}", (string)e.UserState, e.Error.ToString()));
}
else
{
message = (string)e.UserState;
}
// 执行外部传入的回调
actionSendCompletedCallback(message);
}
}
}
运行效果验证
编译运行上述代码后,控制台将首先输出“开始异步发送邮件”与“邮件正在异步发送”,表明主线程未被阻塞。随后,在经过短暂的网络传输延迟后,将在控制台打印“邮件发送结果”及对应的时间戳,并显示成功或失败的状态。这证明了异步机制与回调函数的正确配合。
注意事项与排错
- 依赖项差异:如果在 .NET Core / .NET 5+ 环境中使用,
System.Net.Mail已移除,需使用MailKit等第三方库,其异步 API 基于 Task-based Async Pattern (TAP),用法略有不同(需使用SendAsync并 await 返回值),但回调解耦的思想依然适用。 - 认证信息:示例中的 SMTP 服务器、用户名和密码仅为占位符,实际使用时请务必替换为邮箱服务商提供的有效凭证及正确的 SMTP 端口(通常为 465 SSL 或 587 STARTTLS)。
- 异常捕获:生产环境中建议在
emailCompleted回调内增加更完善的日志记录机制,以便排查网络超时或认证失败的具体原因。
以上就是C#使用委托实现异步发送邮件及回调处理的详细内容,更多关于C#异步编程与邮件服务集成资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









