2011-08-04 17 views
23

Tengo una acción que envía un correo electrónico simple:SmtpClient.SendAsync bloqueando mi ASP.NET MVC Solicitud

[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 electrónico, yo soy usando el método Async, entonces mi método SendAsync regresa inmediatamente, luego se llama a RedirectToAction. Pero la respuesta (en este caso, una redirección) no es enviada por ASP.NET hasta que se complete client_SendCompleted.

Aquí es lo que estoy tratando de entender:

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

Si pongo un punto de interrupción dentro de client_SendCompleted, el cliente se queda cargando ... hasta que presiono F5 en el depurador.

Respuesta

27

Esto es por diseño. ASP.NET esperará automáticamente a que termine la solicitud de cualquier trabajo asíncrono pendiente antes de finalizar la solicitud si el trabajo asincrónico se inició de una manera que llama al subyacente SynchronizationContext. Esto es para asegurarse de que si su operación asincrónica intente interactuar con el HttpContext, HttpResponse, etc., seguirá existiendo.

Si desea hacer true fire & olvide, debe completar su llamada en ThreadPool.QueueUserWorkItem. Esto obligará a ejecutar en un nuevo subproceso de grupo de subprocesos sin pasar por el SynchronizationContext, por lo que la solicitud se devolverá feliz.

Sin embargo, si por alguna razón el dominio de la aplicación baja mientras su envío aún está en progreso (por ejemplo, si cambió el archivo web.config, dejó un nuevo archivo en la papelera, recicló el grupo de aplicaciones, etc. .) su envío asíncrono sería abruptamente interrumpido. Si te importa eso, echa un vistazo a Phil Haacks WebBackgrounder para ASP.NET, que te permite poner en cola y ejecutar trabajos en segundo plano (como enviar un correo electrónico) de manera que se asegure que finalice correctamente en el caso del dominio de la aplicación apaga.

+2

si uso Task.Factory.StartNew (() => SendEmail(), TaskCreationOptions.LongRunning) ¿Tendré el mismo problema? –

+0

[¿Nunca debería llamar a HostingEnvironment.UnregisterObject?] (Http://stackoverflow.com/questions/16096378/should-i-never-call-hostingenvironment-unregisterobject) – horgh

6

Esta es una interesante. He reproducido el comportamiento inesperado, pero no puedo explicarlo. Seguiré cavando

De todos modos, parece que la solución es poner en cola un hilo de fondo, lo que de alguna manera frustra el propósito de usar SendAsync. Se 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))); 

Lo cual puede así convertirse:

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

envié el error a Microsoft Connect, tal vez usted puede ayudar mediante la votación y hacer clic en "Me puedo reproducir también" https : //connect.microsoft.com/VisualStudio/feedback/details/688210/smtpclient-sendasync-blocking-my-asp-net-mvc-request –

+1

'ThreadPool.QueueUserWorkItem' resolver mi problema. Gracias –

0
+0

No sé si esto es un apropo, pero he estado teniendo problemas con SendAsync bloqueando mi solicitud de MVC 3, también. Tal vez no estoy configurando el código correctamente? Buscando en Google, me encontré con Jeff Widmer's Blog, donde demuestra que llama a Send asincrónicamente. Su ejemplo es un gran punto de partida y, para mí, más simple que luchar con SendAsync. http://weblogs.asp.net/jeffwids/archive/2009/10/12/asynchronously-sending-a-system-net-mail-mailmessage-in-c.aspx?CommentPosted=true#commentmessage – Arnold

+0

Lo que está haciendo es un enfoque genérico para hacer Async. El punto es, ¿por qué el método SendAsync bloquea la solicitud? Algo en el marco es incómodo –

+1

Estoy de acuerdo, algo debe estar mal, al menos en mi marco. Sin embargo, no veo el problema. El correo electrónico se envía, pero el redireccionamiento en mi acción de controlador se ignora. Todavía no he visto un ejemplo completo de SendAsync(). El uso de un enfoque genérico y la especificación imperativa de WaitOne() y Dispose() no causa problemas. No me sorprendería si tengo problemas de configuración con SendAsync(). Gracias. – Arnold

1

Con .Net 4.5.2, se puede hacer esto con 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); 
     }); 

favor, lea esto primero: http://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx

Cuestiones relacionadas