SmtpClient.SendAsync блокирует мой запрос ASP.NET MVC - PullRequest
23 голосов
/ 04 августа 2011

У меня есть действие, которое отправляет простое электронное письмо:

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

Ответы [ 4 ]

27 голосов
/ 18 февраля 2012

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

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

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

6 голосов
/ 03 сентября 2011

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

В любом случае, похоже, решение состоит в том, чтобы поставить в очередь фоновый поток, что побеждает цель использования 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));
        }
    }
}); 
1 голос
/ 27 мая 2015

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

0 голосов
/ 14 сентября 2011
...