【问题标题】:SmtpClient.SendAsync blocking my ASP.NET MVC RequestSmtpClient.SendAsync 阻止我的 ASP.NET MVC 请求
【发布时间】:2011-10-19 14:38:06
【问题描述】:

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

    [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。

【问题讨论】:

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


    【解决方案1】:

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

    如果您想要真正的成功并忘记,您需要将您的电话转入ThreadPool.QueueUserWorkItem。这将强制它在一个新的线程池线程上运行而无需经过 SynchronizationContext,因此请求将愉快地返回。

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

    【讨论】:

    【解决方案2】:

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

    无论如何,解决方案似乎是将后台线程排队,这有悖于使用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));
            }
        }
    }); 
    

    【讨论】:

    【解决方案3】:

    使用 .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

    【讨论】:

      【解决方案4】:

      【讨论】:

      • 我不知道这是否合适,但我也遇到了 SendAsync 阻止我的 MVC 3 请求的问题。也许我没有正确配置代码?谷歌搜索,我遇到了 Jeff Widmer 的博客,他在其中演示了异步调用 Send。他的例子是一个很好的起点,对我来说,比使用 SendAsync 更简单。 weblogs.asp.net/jeffwids/archive/2009/10/12/…
      • 他正在做的是一种通用的异步方法。关键是,为什么 SendAsync 方法会阻止请求?框架中的某些东西很尴尬
      • 我同意,肯定有问题,至少在我的框架中。但是,我没有看到这个问题。确实发送了电子邮件,但我的控制器操作中的重定向被忽略了。我还没有看到 SendAsync() 的完整示例。使用通用方法并强制指定 WaitOne() 和 Dispose() 不会导致任何问题。如果我有 SendAsync() 的配置问题,我不会感到惊讶。谢谢。
      猜你喜欢
      • 2012-05-02
      • 1970-01-01
      • 2015-12-07
      • 1970-01-01
      • 1970-01-01
      • 2012-12-06
      • 2010-11-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多