SmtpClient.SendAsync блокирует мой запрос ASP.NET MVC
У меня есть действие, которое отправляет простое электронное письмо:
[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. Но ответ (в данном случае перенаправление) не отправляется ASP.NET до завершения client_SendCompleted.
Вот что я пытаюсь понять:
При просмотре выполнения в отладчике Visual Studio SendAsync немедленно возвращается (и вызывается RedirectToAction), но ничего не происходит в браузере до отправки сообщения электронной почты?
Если я поставил точку останова внутри client_SendCompleted, клиент останется при загрузке.... пока я не удалю F5 в отладчике.
Ответы
Ответ 1
Это по дизайну. ASP.NET автоматически ждет завершения любой выдающейся работы async до завершения запроса, если работа async была запущена так, что вызывается в базовый SynchronizationContext. Это должно гарантировать, что если ваша операция async пытается взаимодействовать с HttpContext, HttpResponse и т.д., Все равно будет.
Если вы хотите сделать настоящий огонь и забыть, вам нужно обернуть свой вызов в ThreadPool.QueueUserWorkItem
. Это заставит его работать в новом потоке пула потоков, не пройдя через SynchronizationContext, поэтому запрос затем с радостью вернется.
Обратите внимание, что если по какой-либо причине домен приложения должен был уйти, пока ваша передача еще не была выполнена (например, если вы изменили файл web.config, сбросили новый файл в корзину, пул приложений переработал и т.д. ) ваша асинхронная передача будет внезапно прервана. Если вас это волнует, взгляните на Phil Haacks WebBackgrounder для 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
Я отправил ошибку в Microsoft Connect https://connect.microsoft.com/VisualStudio/feedback/details/688210/smtpclient-sendasync-blocking-my-asp-net-mvc-request