SmtpClient.SendAsync阻止我的ASP.NET MVC请求

时间:2011-08-04 02:13:37

标签: asp.net-mvc-3 email smtp smtpclient

我有一个发送简单电子邮件的动作:

    [HttpPost, ActionName("Index")]
    public ActionResult IndexPost(ContactForm contactForm)
    {
        if (ModelState.IsValid)
        {
            new EmailService().SendAsync(contactForm.Email, contactForm.Name, contactForm.Subject, contactForm.Body, true);

            return RedirectToAction(MVC.Contact.Success());
        }
        return View(contactForm);
    }

和电子邮件服务:

    public void SendAsync(string fromEmail, string fromName, string subject, string body, bool isBodyHtml)
    {
        MailMessage mailMessage....
        ....
        SmtpClient client = new SmtpClient(settingRepository.SmtpAddress, settingRepository.SmtpPort);

        client.EnableSsl = settingRepository.SmtpSsl;
        client.Credentials = new NetworkCredential(settingRepository.SmtpUserName, settingRepository.SmtpPassword);
        client.SendCompleted += client_SendCompleted;
        client.SendAsync(mailMessage, Tuple.Create(client, mailMessage));
    }

    private void client_SendCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
    {
        Tuple<SmtpClient, MailMessage> data = (Tuple<SmtpClient, MailMessage>)e.UserState;
        data.Item1.Dispose();
        data.Item2.Dispose();

        if (e.Error != null)
        {

        }
    }

当我发送电子邮件时,我使用的是Async方法,然后我的方法SendAsync立即返回,然后调用RedirectToAction。但是,在client_SendCompleted完成之前,ASP.NET不会发送响应(在这种情况下是重定向)。

这是我想要了解的内容:

在Visual Studio调试器中观察执行时,SendAsync立即返回(并调用RedirectToAction),但在发送电子邮件之前,浏览器中没有任何内容发生?

如果我在client_SendCompleted中放置一个断点,客户端将继续加载....直到我在调试器中点击F5。

4 个答案:

答案 0 :(得分:27)

这是设计的。如果异步工作以调用基础 SynchronizationContext 的方式启动, ASP.NET 将自动等待任何未完成的异步工作完成,然后才能完成请求。这是为了确保如果您的异步操作尝试与 HttpContext HttpResponse 等进行交互,它仍将存在。

如果你想做真正的火灾&amp;忘了,你需要在ThreadPool.QueueUserWorkItem中打电话。这将强制它在新的线程池线程上运行,而无需通过 SynchronizationContext ,因此请求将很高兴地返回。

但请注意,如果出于任何原因,当您的发送仍处于进行状态时,应用程序域将停止运行(例如,如果您更改了web.config文件,则将新文件放入bin,应用程序池将被回收,等等。 )您的异步发送将突然中断。如果您关心这一点,请查看Phil Haacks WebBackgrounder以获取 ASP.NET ,让您排队并运行后台工作(如发送电子邮件),以确保在app域关闭的情况下,它会优雅地完成。

答案 1 :(得分:6)

这是一个有趣的问题。我重现了意想不到的行为,但我无法解释。我会继续挖掘。

无论如何,解决方案似乎是将后台线程排队,这种方法在使用SendAsync时失败了。你最终得到了这个:

MailMessage mailMessage = new MailMessage(...);
SmtpClient client = new SmtpClient(...);
client.SendCompleted += (s, e) =>
                            {
                                client.Dispose();
                                mailMessage.Dispose();
                            };

ThreadPool.QueueUserWorkItem(o => 
    client.SendAsync(mailMessage, Tuple.Create(client, mailMessage))); 

哪个也可能成为:

ThreadPool.QueueUserWorkItem(o => {
    using (SmtpClient client = new SmtpClient(...))
    {
        using (MailMessage mailMessage = new MailMessage(...))
        {
            client.Send(mailMessage, Tuple.Create(client, mailMessage));
        }
    }
}); 

答案 2 :(得分:1)

使用 .Net 4.5.2 ,您可以使用ActionMailer.Net执行此操作:

        var mailer = new MailController();
        var msg = mailer.SomeMailAction(recipient);

        var tcs = new TaskCompletionSource<MailMessage>();
        mailer.OnMailSentCallback = tcs.SetResult;
        HostingEnvironment.QueueBackgroundWorkItem(async ct =>
        {
            msg.DeliverAsync();
            await tcs.Task;
            Trace.TraceInformation("Mail sent to " + recipient);
        });

请先阅读此内容:http://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx

答案 3 :(得分:0)