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

时间:2012-01-05 18:09:02

标签: c# asp.net email asynchronous

当用户在我的网站上注册时,我不明白为什么我需要让他“等待”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... 

我这样做了吗?

9 个答案:

答案 0 :(得分:25)

我在这里投了很多好建议......比如确保记得使用IDisposable(我完全不知道)。我还意识到在另一个线程中手动捕获错误是多么重要,因为没有上下文 - 我一直在研究一个理论,我应该让ELMAH处理所有事情。此外,进一步的探索让我意识到我忘记了在邮件消息上使用IDisposable。

对Richard的回应,虽然我看到线程解决方案可以正常工作(正如我的第一个例子中所述),只要我抓住错误......但是如果IIS完全爆炸,那还有一些可怕的事实错误没有被捕获。这告诉我ASP.NET/IIS从来没有意味着你这样做...这就是为什么我倾向于继续使用.BeginInvoke / delegates而不是因为当出现问题时它不会弄乱IIS并且似乎在ASP.NET中更受欢迎。

为了回应ASawyer,我对SMTP客户端内置的.SendAsync感到非常惊讶。我玩了一段时间的解决方案,但它似乎并不适合我。虽然我可以跳过执行SendAsync的代码客户端,但页面仍然“等待”,直到SendCompleted事件完成。我的目标是让用户和页面向前移动,同时在后台发送电子邮件。我有一种感觉,我可能仍然会做错事......所以如果有人来这里,他们可能想自己尝试一下。

除了ELMAH.MVC错误记录之外,这是我完全解决100%异步发送电子邮件的方法。我决定使用示例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.")));
    }
}

答案 1 :(得分:6)

从.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);
    }
} 

答案 2 :(得分:3)

您使用.Net SmtpClient发送电子邮件吗? It can send asynch messages already

编辑 - 如果Emailer mailer = new Emailer();不是SmtpClient的包装器,那么我想象的就没那么有用了。

答案 3 :(得分:3)

如果你使用的是.Net的SmtpClient和MailMessage类,你应该注意几件事。首先,期望发送错误,因此陷阱和处理它们。其次,在.Net 4中,这些类有一些变化,现在都实现了IDisposable(自3.5以来的MailMessage,4.0中的新的SmtpClient)。因此,您应该使用块或显式处理来创建SmtpClient和MailMessage。这是一些人们不知道的突破性变化。

有关使用异步发送时处理的更多信息,请参阅此SO问题:

What are best practices for using SmtpClient, SendAsync and Dispose under .NET 4.0

答案 4 :(得分:3)

线程在这里不是错误的选择,但是如果你自己没有处理异常,它会冒泡并崩溃你的进程。你在哪个线程上做这个并不重要。

所以代替mailer.SendTheMail()试试这个:

new Thread(() => { 
  try 
  {
    mailer.SendTheMail();
  }
  catch(Exception ex)
  {
    // Do something with the exception
  }
});

更好的是,如果可以,请使用SmtpClient的异步功能。你仍然需要处理异常。

我甚至建议您查看.Net 4的新Parallet任务库。它具有额外的功能,可以处理异常情况,并与ASP.Net的线程池配合使用。

答案 5 :(得分:2)

那么,为什么不单独处理发送电子邮件的单独的轮询/服务呢?因此,允许您的注册回发仅在写入数据库/消息队列所花费的时间内执行,并延迟发送电子邮件直到下一个轮询间隔。

我刚才正在考虑同样的问题而且我认为我真的不想在服务器回发请求中发起电子邮件发送。服务网页背后的过程应该有兴趣尽快回复用户,你尝试做的工作越多就越慢。

查看Command Query Segregation Principal(http://martinfowler.com/bliki/CQRS.html)。 Martin Fowler解释说,在操作的命令部分中可以使用与查询部分中使用的不同的模型。在这种情况下,命令将是#34;注册用户",查询将是激活电子邮件,使用松散的类比。相关的引用可能是:

  

通过单独的模型,我们通常表示不同的对象模型,可能在不同的逻辑过程中运行

另外值得一读的是关于CQRS的维基百科文章(http://en.wikipedia.org/wiki/Command%E2%80%93query_separation)。这突出的重点是:

  

它显然是作为编程指南,而不是良好编码的规则

意思是,在代码,程序执行和程序员理解将受益的地方使用它。这是一个很好的示例场景。

这种方法的另一个好处是可以消除所有可能带来的所有多线程问题和头痛问题。

答案 6 :(得分:1)

我为我的项目工作过相同的问题:

首先尝试Thread:你   - 我放松了背景   - 异常处理问题
  - 通常说,Thread在IIS ThreadPool上是个坏主意

所以我切换并试用asynchronously
  - 在asp.net Web应用程序中,“异步”是fake。它只是放入队列调用并打开上下文

所以我创建了Windows服务并通过sql table:happy end

来检索值

因此,对于快速解决方案:从ajax侧进行异步调用告诉用户fake是,但继续在mvc控制器中发送作业

答案 7 :(得分:1)

使用这种方式 -

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 ";
        }

答案 8 :(得分:0)

如果你想检测泄漏,那么你需要使用这样的探查器:

http://memprofiler.com/

我的解决方案没有看到任何错误的,但几乎可以保证这个问题将被视为主观的。

另一个选择是使用jQuery对服务器进行ajax调用并激发电子邮件流。这样,用户界面就不会被锁定。

祝你好运!

马特