简体   繁体   中英

Asynchronously sending Emails in C#?

I'm developing an application where a user clicks/presses enter on a certain button in a window, the application does some checks and determines whether to send out a couple of emails or not, then show another window with a message.

My issue is, sending out the 2 emails slows the process noticeably, and for some (~8) seconds the first window looks frozen while it's doing the sending.

Is there any way I can have these emails sent on the background and display the next window right away?

Please don't limit your answer with "use X class" or "just use X method" as I am not all too familiarized with the language yet and some more information would be highly appreciated.

Thanks.

As of .NET 4.5 SmtpClient implements async awaitable method SendMailAsync . As a result, to send email asynchronously is as following:

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

As it's a small unit of work you should use ThreadPool.QueueUserWorkItem for the threading aspect of it. If you use the SmtpClient class to send your mail you could handle the SendCompleted event to give feedback to the user.

ThreadPool.QueueUserWorkItem(t =>
{
    SmtpClient client = new SmtpClient("MyMailServer");
    MailAddress from = new MailAddress("me@mydomain.com", "My Name", System.Text.Encoding.UTF8);
    MailAddress to = new MailAddress("someone@theirdomain.com");
    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();
}

By creating a fresh SMTP client each time this will allow you to send out emails simultaneously.

It's not too complicated to simply send the message on a separate thread:

using System.Net.Mail;

Smtp.SendAsync(message);

Or if you want to construct the whole message on the separate thread instead of just sending it asynchronously:

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

SmtpClient.SendAsync Method

Sample

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("jane@contoso.com", 
               "Jane " + (char)0xD8+ " Clayton", 
            System.Text.Encoding.UTF8);
            // Set destinations for the e-mail message.
            MailAddress to = new MailAddress("ben@contoso.com");
            // 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.");
        }
    }
}

Here is a fire and forget approach together with async using .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);
});

where BackgroundTaskRunner is:

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

Works like a charm on Azure App Services.

Just because this is a little vague...I will be brief...

There are a lot of ways to do asynchronous or parallel work in c#/.net etc.

The fastest way to do what you want is to use a background worker thread which will avoid locking up your UI.

A tip with background worker threads : you cannot directly update the UI from them (thread affinity and Marshalling is just something you learn to deal with...)

Another thing to consider...if you use the standard System.Net.Mail type stuff to send the emails...be careful how you craft your logic. If you isolate it all in some method and call it over and over, it will likely have to tear down and rebuild the connection to the mail server each time and the latency involved in authentication etc will still slow the whole thing down unnecessarily. Send multiple e-mails through a single open connection to the mail server when possible.

Try this:

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

What you want to do is run the e-mail task on a separate thread so the main code can continue processing while the other thread does the e-mail work.

Here is a tutorial on how to do that: Threading Tutorial C#

使用SmtpClient类并使用 System.Net.Mail 命名空间中的方法SendAsync

Using the Task Parallel Library in .NET 4.0, you can do:

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

Also, seecristina manu's blog post about Parallel.Invoke() vs. explicit task management.

i think this is the best way:

public async Task Send(string to, string subject, string body)
    {
        MailMessage mail = new MailMessage();
        SmtpClient SmtpServer = new SmtpClient("smtp.mail.yahoo.com");
        mail.From = new MailAddress("yourEmail@email.com", "sender name");
        mail.To.Add(to);
        mail.Subject = subject;
        mail.Body = body;
        mail.IsBodyHtml = true;
        SmtpServer.Port = 587;
        SmtpServer.Credentials = new System.Net.NetworkCredential("yourEmail@email.com", "your key");
        SmtpServer.EnableSsl = true;

       await Task.Run(() =>
       {
           SmtpServer.SendAsync(mail, null);
       });
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM