在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... 

我这样做对吗?

在ASP.NET中异步发送电子邮件的正确方式…(我做得对吗?)

我在这里投票支持了很多好建议。。。比如确保记住使用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