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

发布于 2024-11-28 07:39:01 字数 1679 浏览 0 评论 0原文

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

    [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)
        {

        }
    }

当我发送电子邮件时,我使用异步方法,然后我的方法 SendAsync 立即返回,然后调用 RedirectToAction。但在 client_SendCompleted 完成之前,ASP.NET 不会发送响应(在本例中为重定向)。

这就是我想要理解的:

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

如果我在 client_SendCompleted 中放置断点,客户端将保持加载状态......直到我在调试器中按 F5。

I have a Action that sends a simple email:

    [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);
    }

And a email service:

    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)
        {

        }
    }

When I send a email, I am using Async method, then my method SendAsync return immediately, then RedirectToAction is called. But the response(in this case a redirect) isn´t sent by ASP.NET until client_SendCompleted is completed.

Here's what I'm trying to understand:

When watching the execution in Visual Studio debugger, the SendAsync returns immediately (and RedirectToAction is called), but nothing happens in the browser until email is sent?

If i put a breakpoint inside client_SendCompleted, the client stay at loading.... until I hit F5 at debugger.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

雨落星ぅ辰 2024-12-05 07:39:01

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

如果你想做真正的火&忘记了,您需要将调用包装在 ThreadPool.QueueUserWorkItem 中。这将强制它在新的线程池线程上运行,而不通过 SynchronizationContext,因此请求将愉快地返回。

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

This is by design. ASP.NET will automatically wait for any outstanding async work to finish before finishing the request if the async work was kicked off in a way that calls into the underlying SynchronizationContext. This is to ensure that if your async operation tries to interact with the HttpContext, HttpResponse, etc. it will still be around.

If you want to do true fire & forget, you need to wrap your call in ThreadPool.QueueUserWorkItem. This will force it to run on a new thread pool thread without going through the SynchronizationContext, so the request will then happily return.

Note however, that if for any reason the app domain were to go down while your send was still in progress (e.g. if you changed the web.config file, dropped a new file into bin, the app pool recycled, etc.) your async send would be abruptly interrupted. If you care about that, take a look at Phil Haacks WebBackgrounder for ASP.NET, which let's you queue and run background work (like sending an email) in such a way that will ensure it gracefully finishes in the case the app domain shuts down.

摘星┃星的人 2024-12-05 07:39:01

这是一件有趣的事。我重现了意外的行为,但无法解释。我会继续挖掘。

无论如何,解决方案似乎是对后台线程进行排队,这违背了使用 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));
        }
    }
}); 

This is an interesting one. I've reproduced the unexpected behaviour, but I can't explain it. I'll keep digging.

Anyway the solution seems to be to queue a background thread, which kind of defeats the purpose in using SendAsync. You end up with this:

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))); 

Which may as well become:

ThreadPool.QueueUserWorkItem(o => {
    using (SmtpClient client = new SmtpClient(...))
    {
        using (MailMessage mailMessage = new MailMessage(...))
        {
            client.Send(mailMessage, Tuple.Create(client, mailMessage));
        }
    }
}); 
风流物 2024-12-05 07:39:01

.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

With .Net 4.5.2, you can do this with 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);
        });

Please read this first: http://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文