在ASP.NET中异步发送电子邮件的正确方法…(我做得对吗?)
当用户在我的网站上注册时,我不明白为什么我需要让他“等待”smtp通过才能获得激活电子邮件。
我决定异步启动这段代码,这是一次冒险。
让我们想象一下我有一个方法,例如:
private void SendTheMail() { // Stuff }
我的第一个虽然是线程。 我这样做了:
Emailer mailer = new Emailer(); Thread emailThread = new Thread(() => mailer.SendTheMail()); emailThread.Start();
这工作……直到我决定测试它的error handling能力。 我故意破坏了我的web.config中的SMTP服务器地址并尝试了它。 可怕的结果是IIS基本上BARFED与w3wp.exe上的未处理exception错误(这是一个Windows错误!多么极端……)ELMAH(我的错误记录器)没有捕获它并且IIS重新启动所以网站上的任何人都有他们的会话被删除了。 完全不可接受的结果!
我的下一个想法是,对异步代表进行一些研究。 这看起来效果更好,因为在异步委托中处理exception(与上面的线程示例不同)。 但是,我担心的是我做错了还是我可能导致内存泄漏。
这就是我正在做的事情:
Emailer mailer = new Emailer(); AsyncMethodCaller caller = new AsyncMethodCaller(mailer.SendMailInSeperateThread); caller.BeginInvoke(message, email.EmailId, null, null); // Never EndInvoke...
我这样做了吗?
我在这里投了很多好建议……比如确保记得使用IDisposable(我完全不知道)。 我也意识到在另一个线程中手动捕获错误是多么重要,因为没有上下文 – 我一直在研究一个理论,我应该让ELMAH处理所有事情。 此外,进一步的探索让我意识到我忘了在邮件消息上使用IDisposable。
为了回应理查德,虽然我看到线程解决方案可以工作(正如我的第一个例子中所示),只要我抓住错误……如果错误不是,那么IIS完全爆炸的事实仍然是可怕的。抓住了。 这告诉我,ASP.NET / IIS从来没有意味着你这样做…这就是为什么我倾向于继续使用.BeginInvoke / delegates而不是因为当出现问题时它不会弄乱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实现异步等待方法SendMailAsync
。 因此,异步发送电子邮件如下:
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发送电子邮件吗? 它可以发送异步消息 。
编辑 – 如果Emailer mailer = new Emailer();
不是SmtpClient的包装器,我想象这不会那么有用。
线程在这里不是错误的选择,但是如果你自己没有处理exception,它会冒泡并崩溃你的进程。 你做哪个线程并不重要。
所以代替mailer.SendTheMail()试试这个:
new Thread(() => { try { mailer.SendTheMail(); } catch(Exception ex) { // Do something with the exception } });
更好的是,如果可以的话,使用SmtpClient的异步function。 你仍然需要处理exception。
我甚至建议您查看.Net 4的新Parallet任务库。 它具有额外的function,可以让您处理exception情况,并与ASP.Net的线程池一起使用。
如果您使用.Net的SmtpClient和MailMessage类,您应该注意几件事。 首先,期望发送错误,因此陷阱和处理它们。 其次,在.Net 4中,这些类有一些变化,现在都实现了IDisposable(自3.5以来的MailMessage,4.0中的新的SmtpClient)。 因此,您应该使用块或显式处理来创建SmtpClient和MailMessage。 这是一些人们不知道的突破性变化。
有关使用异步发送时处置的更多信息,请参阅此SO问题:
在.NET 4.0下使用SmtpClient,SendAsync和Dispose的最佳实践是什么
那么,为什么不单独处理发送电子邮件的单独的轮询/服务呢? 因此,允许您的注册回发仅在写入数据库/消息队列所花费的时间内执行,并延迟发送电子邮件直到下一个轮询间隔。
我刚才正在思考同样的问题,我想我甚至不想在服务器回发请求中发起电子邮件发送。 服务网页背后的过程应该有兴趣尽快回复用户,你尝试做的工作越多越慢。
查看Command Query Segregation Principal( http://martinfowler.com/bliki/CQRS.html )。 Martin Fowler解释说,可以在查询部分中使用的操作的命令部分中使用不同的模型。 在这种情况下,命令将是“注册用户”,查询将是激活电子邮件,使用松散的类比。 相关的引用可能是:
通过单独的模型,我们通常表示不同的对象模型,可能在不同的逻辑过程中运行
另外值得一读的是关于CQRS的维基百科文章( http://en.wikipedia.org/wiki/Command%E2%80%93query_separation )。 这个亮点的一个重点是:
它显然是作为编程指南而不是良好编码的规则
意思是,在代码,程序执行和程序员理解将受益的地方使用它。 这是一个很好的示例场景。
这种方法的另一个好处是可以消除所有可能带来的所有multithreading问题和令人头疼的问题。
我为我的项目工作过同样的问题:
首先尝试Thread
就像你一样:
– 我松散的背景
– exception处理问题
– 通常说, Thread
在IIS ThreadPool上是个坏主意
所以我切换并asynchronously
尝试:
– ‘异步’在asp.net Web应用程序中是fake
。 它只是放入队列调用并打开上下文
所以我通过sql表创建windows服务并检索值: 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(""); str.Append(""); str.Append(""); str.Append("
"); str.Append(""); str.Append(""); //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 "; }
如果要检测泄漏,则需要使用如下所示的分析器:
我没有看到你的解决方案有任何问题 ,但几乎可以保证你这个问题将被视为主观的。
另一个选择是使用jQuery对服务器进行ajax调用并激发电子邮件流。 这样,UI就不会被锁定。
祝好运!
马特