18 votos

SmtpClient.SendAsync bloqueo mi solicitud de ASP.NET MVC

Tengo una Acción que envía un simple correo electrónico:

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

Y un servicio de correo electrónico:

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

        }
    }

Cuando envío un correo, yo estoy usando el método Asincrónico, entonces mi método SendAsync regresar inmediatamente, a continuación, RedirectToAction se llama. Pero la respuesta(en este caso una redirección) no es enviado por ASP.NET hasta client_SendCompleted se ha completado.

Esto es lo que estoy tratando de entender:

Al ver la ejecución en el depurador de Visual Studio, el SendAsync devuelve inmediatamente (y RedirectToAction se llama), pero no pasa nada en el navegador hasta que se envía el correo electrónico?

Si puedo poner un breakpoint dentro de client_SendCompleted, el cliente permanezca en la carga.... hasta que llegué a F5 en el depurador.

23voto

Damian Edwards Puntos 3062

Esto es así por diseño. ASP.NET automáticamente esperar cualquier pendientes async trabajo que terminar antes de finalizar el pedido si la async trabajo se inició de una manera que pone en el subyacente SynchronizationContext. Esto es para asegurar que si su async operación intenta interactuar con el HttpContext, HttpResponse, etc. todavía será de alrededor.

Si usted desea hacer la verdadera fire & forget, deberá ajustar su llamada en ThreadPool.QueueUserWorkItem. Esto hará que se ejecute en un nuevo grupo de subprocesos subproceso sin pasar por el SynchronizationContext, por lo que la solicitud se haga felices para regresar.

Tenga en cuenta sin embargo, que si por alguna razón el dominio de la aplicación ir hacia abajo, mientras que su envío fue todavía en curso (por ejemplo, si ha cambiado la web.archivo de configuración, se redujo un nuevo archivo en la papelera, la aplicación de la piscina de reciclado, etc.) su async enviar sería abruptamente interrumpido. Si usted se preocupa por eso, echar un vistazo a Phil Haacks WebBackgrounder para ASP.NETque vamos a la cola y ejecutar un trabajo de fondo (como el envío de un correo electrónico), de tal manera que se asegure con gracia acabados en el caso de que el dominio de la aplicación se cierra.

4voto

TheCodeKing Puntos 11632

Esto es interesante. Yo he reproducido el comportamiento inesperado, pero no puedo explicarlo. Voy seguir cavando.

De todas formas la solución parece estar en la cola un subproceso de fondo, que tipo de derrotas el propósito en el uso de SendAsync . Termina con esto:

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

Así que puede ser:

ThreadPool.QueueUserWorkItem(o => {
    using (SmtpClient client = new SmtpClient(...))
    {
        using (MailMessage mailMessage = new MailMessage(...))
        {
            client.Send(mailMessage, Tuple.Create(client, mailMessage));
        }
    }
}); 

0voto

Fujiy Puntos 2155

Envié el error a Microsoft Connect https://connect.microsoft.com/VisualStudio/feedback/details/688210/smtpclient-sendasync-blocking-my-asp-net-mvc-request

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X