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 в отладчике.


person Felipe Pessoto    schedule 04.08.2011    source источник


Ответы (4)


Это по дизайну. ASP.NET будет автоматически ожидать завершения любой незавершенной асинхронной работы перед завершением запроса, если асинхронная работа была запущена таким образом, что вызывается базовый SynchronizationContext. Это делается для того, чтобы ваша асинхронная операция могла взаимодействовать с HttpContext, HttpResponse и т. д., но она не исчезла.

Если вы хотите по-настоящему выстрелить и забыть, вам нужно обернуть вызов в ThreadPool.QueueUserWorkItem. Это заставит его работать в новом потоке пула потоков без прохождения через SynchronizationContext, поэтому запрос будет успешно возвращен.

Однако обратите внимание, что если по какой-либо причине домен приложения отключится, пока ваша отправка все еще выполняется (например, если вы изменили файл web.config, поместили новый файл в корзину, переработали пул приложений и т. д.), ваш асинхронный отправка будет резко прервана. Если вас это волнует, взгляните на Фила Хаака WebBackgrounder для ASP.NET, который позволяет ставить в очередь и выполнять фоновую работу (например, отправку электронной почты) таким образом, чтобы обеспечить изящно завершается в случае закрытия домена приложения.

person Damian Edwards    schedule 18.02.2012
comment
если я использую Task.Factory.StartNew(() =› SendEmail(), TaskCreationOptions.LongRunning) у меня будет такая же проблема? - person Felipe Pessoto; 24.02.2012

Это интересно. Я воспроизвел неожиданное поведение, но не могу его объяснить. Я буду копать.

В любом случае решение, похоже, состоит в том, чтобы поставить в очередь фоновый поток, что противоречит цели использования 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));
        }
    }
}); 
person TheCodeKing    schedule 03.09.2011
comment
Я отправил сообщение об ошибке в Microsoft Connect, возможно, вы можете помочь, проголосовав и нажав на кнопку Я тоже могу воспроизвести connect.microsoft.com/VisualStudio/feedback/details/688210/ - person Felipe Pessoto; 14.09.2011

В .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

person malix    schedule 27.05.2015

Я отправил сообщение об ошибке в Microsoft Connect https://connect.microsoft.com/VisualStudio/feedback/details/688210/smtpclient-sendasync-blocking-my-asp-net-mvc-request

person Felipe Pessoto    schedule 13.09.2011
comment
Я не знаю, уместно ли это, но у меня были проблемы с SendAsync, блокирующим мой запрос MVC 3. Может я не правильно настраиваю код? Погуглив, я наткнулся на блог Джеффа Видмера, где он демонстрирует асинхронный вызов Send. Его пример — отличная отправная точка, и для меня он проще, чем борьба с SendAsync. weblogs.asp.net/jeffwids/archive/2009/10/12/ - person Arnold; 24.09.2011
comment
То, что он делает, — это общий подход к асинхронности. Дело в том, почему метод SendAsync блокирует запрос? Что-то в рамках несуразное - person Felipe Pessoto; 24.09.2011
comment
Я согласен, что-то должно быть не так, по крайней мере, в моих рамках. Однако я не вижу проблемы. Электронная почта отправляется, но перенаправление в моем действии контроллера игнорируется. Я еще не видел полного примера SendAsync(). Использование универсального подхода и императивное указание WaitOne() и Dispose() не вызывает проблем. Я не удивлюсь, если у меня возникнут проблемы с конфигурацией SendAsync(). Спасибо. - person Arnold; 30.09.2011