在ASP.NET中异步发送电子邮件的正确方式…(我做得对吗?)
本文关键字:方式 异步 NET ASP 电子邮件 | 更新日期: 2023-09-27 18:19:29
当用户在我的网站上注册时,我不明白为什么我需要让他"等待"smtp通过,以便他收到激活电子邮件。
我决定异步启动这个代码,这是一次冒险。
让我们想象一下我有一个方法,比如:
private void SendTheMail() { // Stuff }
我的第一次。。正在穿线。我做到了:
Emailer mailer = new Emailer();
Thread emailThread = new Thread(() => mailer.SendTheMail());
emailThread.Start();
这很有效。。。直到我决定测试它的错误处理能力。我故意破坏了web.config中的SMTP服务器地址并尝试了它。可怕的结果是,IIS基本上是BARFED,w3wp.exe上出现了一个未处理的异常错误(这是一个windows错误!多么极端…)ELMAH(我的错误记录器)没有捕捉到它,IIS被重新启动,因此网站上的任何人都被删除了会话。完全不能接受的结果!
我的下一个想法是对异步委托进行一些研究。这似乎效果更好,因为异常是在异步委托中处理的(与上面的线程示例不同)。然而,我担心我是否做错了,或者可能是我造成了内存泄漏。
我在做什么:
Emailer mailer = new Emailer();
AsyncMethodCaller caller = new AsyncMethodCaller(mailer.SendMailInSeperateThread);
caller.BeginInvoke(message, email.EmailId, null, null);
// Never EndInvoke...
我这样做对吗?
我在这里投票支持了很多好建议。。。比如确保记住使用IDisposable(我完全不知道)。我还意识到,在另一个线程中手动捕获错误是多么重要,因为没有上下文——我一直在研究一个理论,即我应该让ELMAH处理所有事情。此外,进一步的探索让我意识到我也忘记了在邮件中使用IDisposable。
作为对Richard的回应,尽管我看到线程解决方案可以工作(正如我在第一个例子中所建议的那样),只要我捕捉到错误。。。如果没有发现错误,IIS就会完全崩溃,这一事实仍然令人担忧。这告诉我ASP.NET/IIS从来就不想让你这么做。。。这就是为什么我倾向于继续使用。改为BeginInvoke/deletes,因为当出现问题时,它不会扰乱IIS,而且似乎在ASP.NET中更受欢迎。
作为对ASawyer的回应,我完全惊讶于SMTP客户端中内置了.SendAsync。我用这个解决方案玩了一段时间,但它似乎对我没有效果。虽然我可以跳过执行SendAsync的代码的客户端,但页面仍然"等待",直到SendCompleted事件完成。我的目标是让用户和页面向前移动,同时在后台发送电子邮件。我有一种感觉,我可能还是做错了什么。。。所以,如果有人来了,他们可能想自己试试。
以下是我如何100%异步发送电子邮件的完整解决方案,以及ELMAH.MVC错误日志。我决定使用示例2的扩展版本:
public void SendThat(MailMessage message)
{
AsyncMethodCaller caller = new AsyncMethodCaller(SendMailInSeperateThread);
AsyncCallback callbackHandler = new AsyncCallback(AsyncCallback);
caller.BeginInvoke(message, callbackHandler, null);
}
private delegate void AsyncMethodCaller(MailMessage message);
private void SendMailInSeperateThread(MailMessage message)
{
try
{
SmtpClient client = new SmtpClient();
client.Timeout = 20000; // 20 second timeout... why more?
client.Send(message);
client.Dispose();
message.Dispose();
// If you have a flag checking to see if an email was sent, set it here
// Pass more parameters in the delegate if you need to...
}
catch (Exception e)
{
// This is very necessary to catch errors since we are in
// a different context & thread
Elmah.ErrorLog.GetDefault(null).Log(new Error(e));
}
}
private void AsyncCallback(IAsyncResult ar)
{
try
{
AsyncResult result = (AsyncResult)ar;
AsyncMethodCaller caller = (AsyncMethodCaller)result.AsyncDelegate;
caller.EndInvoke(ar);
}
catch (Exception e)
{
Elmah.ErrorLog.GetDefault(null).Log(new Error(e));
Elmah.ErrorLog.GetDefault(null).Log(new Error(new Exception("Emailer - This hacky asynccallback thing is puking, serves you right.")));
}
}
作为.NET 4.5的一部分,SmtpClient实现异步可唤醒方法CCD_ 1。因此,异步发送电子邮件如下所示:
public async Task SendEmail(string toEmailAddress, string emailSubject, string emailMessage)
{
var message = new MailMessage();
message.To.Add(toEmailAddress);
message.Subject = emailSubject;
message.Body = emailMessage;
using (var smtpClient = new SmtpClient())
{
await smtpClient.SendMailAsync(message);
}
}
如果您正在使用.Net的SmtpClient和MailMessage类,您应该注意以下几点。首先,预计发送时会出现错误,因此捕获并处理它们。其次,在.Net 4中,这些类发生了一些更改,现在都实现了IDisposable(MailMessage自3.5起,SmtpClient在4.0中新增)。因此,您创建的SmtpClient和MailMessage应该使用块封装或显式处理。这是一个有些人没有意识到的突破性变化。
有关使用异步发送时处理的更多信息,请参阅此SO问题:
在.NET 4.0 下使用SmtpClient、SendAsync和Dispose的最佳做法是什么?
您是否使用.NET SmtpClient发送电子邮件?它已经可以发送异步消息了。
编辑-如果Emailer mailer = new Emailer();
不是SmtpClient的包装器,我想这就没有那么有用了。
线程不是错误的选项,但如果你自己不处理异常,它会弹出并导致进程崩溃。你在哪个线程上这样做并不重要。
所以不用mailer.SendTheMail(),试试这个:
new Thread(() => {
try
{
mailer.SendTheMail();
}
catch(Exception ex)
{
// Do something with the exception
}
});
更好的是,如果可以的话,可以使用SmtpClient的异步功能。不过,您仍然需要处理异常。
我甚至建议你看看.Net 4的新Parallet任务库。它具有额外的功能,可以让您处理特殊情况,并与ASP.Net的线程池配合良好。
那么,为什么不单独提供一个专门处理发送电子邮件的轮询器/服务呢?因此,只允许在写入数据库/消息队列所需的时间内执行注册日志,并将电子邮件的发送延迟到下一个轮询间隔。
我刚才也在思考同样的问题,我想我真的不想在服务器回邮请求中启动电子邮件发送。提供网页的过程应该对尽快回复用户感兴趣,你尝试做的工作越多,速度就会越慢
查看命令查询隔离主体(http://martinfowler.com/bliki/CQRS.html)。Martin Fowler解释说,操作的命令部分可以使用与查询部分不同的模型。在这种情况下,命令将是"注册用户",查询将是激活电子邮件,使用松散的类比。相关的报价可能是:
单独的模型通常指不同的对象模型,可能运行在不同的逻辑过程中
同样值得一读的是维基百科上关于CQRS的文章(http://en.wikipedia.org/wiki/Command%E2%80%93query_separation)。本文强调的一个重要点是:
它显然是作为一个编程指南,而不是一个良好编码的规则
也就是说,在你的代码、程序执行和程序员理解会受益的地方使用它。这是一个很好的例子。
这种方法还有一个额外的好处,那就是消除了所有的mufti线程问题和所有可能带来的头痛。
我为我的项目处理了同样的问题:
首次尝试Thread
:
-我失去上下文
-异常处理问题
-通常说,Thread
在IIS线程池上是个坏主意
所以我切换并尝试使用asynchronously
:
-"异步"是asp.net web应用程序中的fake
。它只是放入队列调用并切换上下文
所以我制作了windows服务,并通过sql表检索值:happy end
因此,对于快速解决方案:从ajax
侧进行异步调用,告诉用户fake
是的,但在mvc控制器中继续发送作业
使用这种方式-
private void email(object parameters)
{
Array arrayParameters = new object[2];
arrayParameters = (Array)parameters;
string Email = (string)arrayParameters.GetValue(0);
string subjectEmail = (string)arrayParameters.GetValue(1);
if (Email != "Email@email.com")
{
OnlineSearch OnlineResult = new OnlineSearch();
try
{
StringBuilder str = new StringBuilder();
MailMessage mailMessage = new MailMessage();
//here we set the address
mailMessage.From = fromAddress;
mailMessage.To.Add(Email);//here you can add multiple emailid
mailMessage.Subject = "";
//here we set add bcc address
//mailMessage.Bcc.Add(new MailAddress("bcc@site.com"));
str.Append("<html>");
str.Append("<body>");
str.Append("<table width=720 border=0 align=left cellpadding=0 cellspacing=5>");
str.Append("</table>");
str.Append("</body>");
str.Append("</html>");
//To determine email body is html or not
mailMessage.IsBodyHtml = true;
mailMessage.Body = str.ToString();
//file attachment for this e-mail message.
Attachment attach = new Attachment();
mailMessage.Attachments.Add(attach);
mailClient.Send(mailMessage);
}
}
protected void btnEmail_Click(object sender, ImageClickEventArgs e)
{
try
{
string To = txtEmailTo.Text.Trim();
string[] parameters = new string[2];
parameters[0] = To;
parameters[1] = PropCase(ViewState["StockStatusSub"].ToString());
Thread SendingThreads = new Thread(email);
SendingThreads.Start(parameters);
lblEmail.Visible = true;
lblEmail.Text = "Email Send Successfully ";
}
如果你想检测泄漏,那么你需要使用像这样的探查器:
http://memprofiler.com/
我认为你的解决方案没有任何错误,但几乎可以保证这个问题会被视为主观问题。
另一种选择是使用jQuery对服务器进行ajax调用,并引发电子邮件流。这样,UI就不会被锁定。
祝你好运!
Matt