我有一个发送简单电子邮件的动作:
[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。
答案 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)