2010-08-04 112 views
30

Estoy desarrollando una aplicación donde un usuario hace clic/presiona ingresar en un botón determinado en una ventana, la aplicación realiza algunas comprobaciones y determina si envía un par de correos electrónicos o no, luego muestra otra ventana con un mensaje.¿Enviar correo electrónico de forma asíncrona en C#?

Mi problema es que enviar los 2 correos electrónicos ralentiza notablemente el proceso, y durante algunos (~ 8) segundos la primera ventana se ve congelada mientras realiza el envío.

¿Hay alguna manera en que pueda hacer que estos correos electrónicos se envíen en segundo plano y mostrar la siguiente ventana de inmediato?

Por favor, no limite su respuesta con "use la clase X" o "simplemente use el método X", ya que aún no estoy muy familiarizado con el idioma y se agradecerá más información.

Gracias.

+0

¿Alguna vez su aplicación tiene que hacer algo con el resultado (éxito, falla, otro) del envío del correo electrónico? Esto puede afectar si alguna vez tiene que esperar o buscar el resultado de enviar el correo (y por lo tanto * cómo * lo hace), o si se trata más bien de un tipo de "disparar y olvidar". – JaredReisinger

+0

En realidad, no es más una cuestión de "olvida y olvida" como la llamas. Es algo bastante simple, creo, pero obviamente no tanto para mí. Estoy viendo las diferentes respuestas en este momento y los resultados de las pruebas. –

+0

Si miras mi respuesta, te he dado un ejemplo pequeño (¡pero debería estar funcionando, aunque no probado!) De cómo enviar los correos electrónicos no solo de forma asíncrona, sino de cómo manejar los resultados * y * mostrar los comentarios a la IU . – James

Respuesta

54

A partir de .NET 4.5 SmtpClient implementa el método async awaitable SendMailAsync. Como resultado, para enviar correo electrónico de forma asíncrona es el siguiente:

public async Task SendEmail(string toEmailAddress, string emailSubject, string emailMessage) 
{ 
    var message = new MailMessage(); 
    message.To.Add(toEmailAddress); 

    message.Subject = emailSubject; 
    message.Body = emailMessage; 

    using (var smtpClient = new SmtpClient()) 
    { 
     await smtpClient.SendMailAsync(message); 
    } 
} 
+0

Esta es la mejor respuesta para los estándares actuales, ya que es la única que admite async/await. –

+0

Este código no funcionará como no host y se definirá la dirección De. – Elisabeth

+13

El hecho es que este código aún esperará a que se envíe el correo. Sería bueno si lo editó para agregar una llamada a este método y lo inicia en un hilo separado. – JCabello

9

No es demasiado complicado para simplemente enviar el mensaje en un hilo separado:

using System.Net.Mail; 

Smtp.SendAsync(message); 

O, si se quiere construir todo el mensaje en el hilo separado en lugar de algo acaba de enviarlo de forma asíncrona:

using System.Threading; 
using System.Net.Mail; 

var sendMailThread = new Thread(() => { 
    var message=new MailMessage(); 
    message.From="from e-mail"; 
    message.To="to e-mail"; 
    message.Subject="Message Subject"; 
    message.Body="Message Body"; 

    SmtpMail.SmtpServer="SMTP Server Address"; 
    SmtpMail.Send(message); 
}); 

sendMailThread.Start(); 
+5

[ObsoleteAttribute ("La alternativa recomendada es System.Net.Mail.SmtpClient. Http://go.microsoft.com/fwlink/?linkid=14202")] – Andrey

+6

También se recomienda la creación de subprocesos para procesos de larga ejecución, no para asincrónicos tareas – Andrey

1

Lo que quiere hacer es ejecutar la tarea de correo electrónico en un hilo separado para que el código principal pueda continuar el procesamiento mientras que el otro hilo funciona el correo electrónico.

Aquí es un tutorial sobre cómo hacerlo: Threading Tutorial C#

8

SmtpClient.SendAsync Method

Muestra

using System; 
using System.Net; 
using System.Net.Mail; 
using System.Net.Mime; 
using System.Threading; 
using System.ComponentModel; 
namespace Examples.SmptExamples.Async 
{ 
    public class SimpleAsynchronousExample 
    { 
     static bool mailSent = false; 
     private static void SendCompletedCallback(object sender, AsyncCompletedEventArgs e) 
     { 
      // Get the unique identifier for this asynchronous operation. 
      String token = (string) e.UserState; 

      if (e.Cancelled) 
      { 
       Console.WriteLine("[{0}] Send canceled.", token); 
      } 
      if (e.Error != null) 
      { 
       Console.WriteLine("[{0}] {1}", token, e.Error.ToString()); 
      } else 
      { 
       Console.WriteLine("Message sent."); 
      } 
      mailSent = true; 
     } 
     public static void Main(string[] args) 
     { 
      // Command line argument must the the SMTP host. 
      SmtpClient client = new SmtpClient(args[0]); 
      // Specify the e-mail sender. 
      // Create a mailing address that includes a UTF8 character 
      // in the display name. 
      MailAddress from = new MailAddress("[email protected]", 
       "Jane " + (char)0xD8+ " Clayton", 
      System.Text.Encoding.UTF8); 
      // Set destinations for the e-mail message. 
      MailAddress to = new MailAddress("[email protected]"); 
      // Specify the message content. 
      MailMessage message = new MailMessage(from, to); 
      message.Body = "This is a test e-mail message sent by an application. "; 
      // Include some non-ASCII characters in body and subject. 
      string someArrows = new string(new char[] {'\u2190', '\u2191', '\u2192', '\u2193'}); 
      message.Body += Environment.NewLine + someArrows; 
      message.BodyEncoding = System.Text.Encoding.UTF8; 
      message.Subject = "test message 1" + someArrows; 
      message.SubjectEncoding = System.Text.Encoding.UTF8; 
      // Set the method that is called back when the send operation ends. 
      client.SendCompleted += new 
      SendCompletedEventHandler(SendCompletedCallback); 
      // The userState can be any object that allows your callback 
      // method to identify this send operation. 
      // For this example, the userToken is a string constant. 
      string userState = "test message1"; 
      client.SendAsync(message, userState); 
      Console.WriteLine("Sending message... press c to cancel mail. Press any other key to exit."); 
      string answer = Console.ReadLine(); 
      // If the user canceled the send, and mail hasn't been sent yet, 
      // then cancel the pending operation. 
      if (answer.StartsWith("c") && mailSent == false) 
      { 
       client.SendAsyncCancel(); 
      } 
      // Clean up. 
      message.Dispose(); 
      Console.WriteLine("Goodbye."); 
     } 
    } 
} 
+2

-1 OP preguntó específicamente si no solo especificaría qué métodos necesitaban usar, querían un ejemplo. – James

+4

@James vaya al enlace, hay un ejemplo allí. "Ejemplos [+] El siguiente ejemplo de código demuestra llamar a este método". – Andrey

+0

Mire de cerca. El ** mensaje. Deshacer() ** es el verdadero culpable aquí, que fue corregido por James en su respuesta. – vibs2006

1

la solución más fácil es crear un BackgroundWorker y empujar los correos en una cola. Luego solo deje que BackgroundWorker revise la cola y envíe cada correo.

Ver también How to: Run an Operation in the Background

+1

cualquier código fuente completo muestra al respecto? – Kiquenet

5

El hecho de que esto es un poco vago ... Voy a ser breve ...

Hay un montón de maneras de hacer el trabajo asíncrono o paralelo en C# /. Net etc.

el más rápido La forma de hacer lo que desea es usar un hilo de trabajador de fondo que evitará bloquear su UI.

Una punta con subprocesos de trabajo de fondo: no se puede actualizar directamente a la interfaz de usuario de ellos (afinidad hilo y Marshalling es sólo algo que se aprende a tratar con ...)

Otra cosa a tener en cuenta ... si se utiliza el tipo de cosas estándar de System.Net.Mail para enviar los correos electrónicos ... tenga cuidado de cómo elabora su lógica. Si aisla todo en algún método y lo llama una y otra vez, probablemente tendrá que demoler y reconstruir la conexión con el servidor de correo cada vez y la latencia involucrada en la autenticación, etc., ralentizará todo el proceso innecesariamente. Envíe múltiples correos electrónicos a través de una única conexión abierta al servidor de correo cuando sea posible.

4

Prueba esto:

var client = new System.Net.Mail.SmtpClient("smtp.server"); 
var message = new System.Net.Mail.MailMessage() { /* provide its properties */ }; 
client.SendAsync(message, null); 
+3

Intenté esta solución, sin embargo SendAsync toma al menos 2 parámetros. Utilicé una sobrecarga correcta pero los correos electrónicos no se envían, probé la depuración y en la línea SendAsync la dirección del destinatario, el asunto, el cuerpo, etc. son correctos. No tengo idea de por qué no los envía cuando Send lo hace. –

+1

$ eton-b: Perdón por el error de sintaxis. Lo arreglé. – kbrimington

1

Utilizando el Task Parallel Library en .NET 4.0, que puede hacer:

Parllel.Invoke(() => { YourSendMailMethod(); }); 

Además, vea cristina manu's el blog post sobre Parallel.Invoke() frente a la gestión de tareas explícito .

+1

Eso no va a funcionar.La función 'Invoke' no regresa hasta que se completen todas las acciones internas. – Gabe

+1

Crud ... tienes razón, por supuesto. Estaba pensando en 'Task.Factory.StartNew()'. – JaredReisinger

22

Como es una pequeña unidad de trabajo, debe usar ThreadPool.QueueUserWorkItem para el aspecto de roscado de la misma. Si usa la clase SmtpClient para enviar su correo, puede manejar el evento SendCompleted para enviar comentarios al usuario.

ThreadPool.QueueUserWorkItem(t => 
{ 
    SmtpClient client = new SmtpClient("MyMailServer"); 
    MailAddress from = new MailAddress("[email protected]", "My Name", System.Text.Encoding.UTF8); 
    MailAddress to = new MailAddress("[email protected]"); 
    MailMessage message = new MailMessage(from, to); 
    message.Body = "The message I want to send."; 
    message.BodyEncoding = System.Text.Encoding.UTF8; 
    message.Subject = "The subject of the email"; 
    message.SubjectEncoding = System.Text.Encoding.UTF8; 
    // Set the method that is called back when the send operation ends. 
    client.SendCompleted += new SendCompletedEventHandler(SendCompletedCallback); 
    // The userState can be any object that allows your callback 
    // method to identify this send operation. 
    // For this example, I am passing the message itself 
    client.SendAsync(message, message); 
}); 

private static void SendCompletedCallback(object sender, AsyncCompletedEventArgs e) 
{ 
     // Get the message we sent 
     MailMessage msg = (MailMessage)e.UserState; 

     if (e.Cancelled) 
     { 
      // prompt user with "send cancelled" message 
     } 
     if (e.Error != null) 
     { 
      // prompt user with error message 
     } 
     else 
     { 
      // prompt user with message sent! 
      // as we have the message object we can also display who the message 
      // was sent to etc 
     } 

     // finally dispose of the message 
     if (msg != null) 
      msg.Dispose(); 
} 

Al crear un cliente SMTP nuevo cada vez esto le permitirá enviar correos electrónicos simultáneamente.

+1

Así que estoy tratando de trabajar en esta respuesta, ya que parece ser la más viable (simplicidad/similar a lo que estoy usando) para mí. Sin embargo, ¿qué se usa exactamente como "estado de usuario"? ¿TENGO que usar un hilo? Simplemente cambié mi método de envío para esto: string someString = "Message"; smtp.SendAsync (message, someString); inútilmente. Implementaré toda tu solución y veré qué estoy haciendo mal. –

+2

@Eton si mira el ejemplo, he indicado lo que hace la variable userState. Es un objeto que se pasa al método de devolución de llamada cuando se produce el evento. En el ejemplo, lo estoy usando como un identificador único, pero básicamente puedes pasar lo que quieras en él. No, no es una necesidad, si no necesita usarlo en el método de devolución de llamada, simplemente pase nulo. – James

+1

@Eton - No, no * tiene * para usar un hilo en absoluto. Si quita el código de envío de correo electrónico de la llamada QueueUserWorkItem y lo coloca directamente detrás del evento click del botón, debería funcionar igual ya que está creando un SmtpClient diferente cada vez y el correo electrónico se está enviando mediante SendAsync (que ganó t bloquear la interfaz de usuario). – James

4

Aquí es fuego y olvidarse enfoque junto con asíncrono usando .Net 4.5.2+:

BackgroundTaskRunner.FireAndForgetTaskAsync(async() => 
{ 
    SmtpClient smtpClient = new SmtpClient(); // using configuration file settings 
    MailMessage message = new MailMessage(); // TODO: Initialize appropriately 
    await smtpClient.SendMailAsync(message); 
}); 

donde es BackgroundTaskRunner :

public static class BackgroundTaskRunner 
{  
    public static void FireAndForgetTask(Action action) 
    { 
     HostingEnvironment.QueueBackgroundWorkItem(cancellationToken => // .Net 4.5.2+ required 
     { 
      try 
      { 
       action(); 
      } 
      catch (Exception e) 
      { 
       // TODO: handle exception 
      } 
     }); 
    } 

    /// <summary> 
    /// Using async 
    /// </summary> 
    public static void FireAndForgetTaskAsync(Func<Task> action) 
    { 
     HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => // .Net 4.5.2+ required 
     { 
      try 
      { 
       await action(); 
      } 
      catch (Exception e) 
      { 
       // TODO: handle exception 
      } 
     }); 
    } 
} 

Funciona como un encanto en Azure App Services.

Cuestiones relacionadas