在C#中异步发送电子邮件?

Eto*_* B. 35 c# email asynchronous visual-studio

我正在开发一个应用程序,用户点击/按下窗口中某个按钮的输入,应用程序执行一些检查并确定是否发送几封电子邮件,然后显示另一个带有消息的窗口.

我的问题是,发送2封电子邮件会显着减慢这个过程,并且在某些(约8)秒内,第一个窗口在进行发送时会看起来冻结.

有什么方法可以在后台发送这些电子邮件并立即显示下一个窗口吗?

请不要使用"使用X类"或"仅使用X方法"来限制您的答案,因为我还不太熟悉该语言,并且将非常感谢更多信息.

谢谢.

Bor*_*itz 68

从.NET 4.5开始,SmtpClient实现异步等待方法 SendMailAsync.因此,异步发送电子邮件如下:

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);
    }
} 
Run Code Online (Sandbox Code Playgroud)

  • 问题是这段代码仍然会等待发送邮件.如果您编辑它以添加对此方法的调用将在单独的线程中启动它将会很好. (16认同)

Jam*_*mes 24

因为它是一个小工作单元,所以你应该使用ThreadPool.QueueUserWorkItem来处理它的线程方面.如果您使用SmtpClient类发送邮件,则可以处理SendCompleted事件以向用户提供反馈.

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();
}
Run Code Online (Sandbox Code Playgroud)

通过每次创建一个新的SMTP客户端,您可以同时发送电子邮件.

  • @Sruly - 最好总是处理实现IDisposable的对象(通常你想把它包装在using语句中,但在这种情况下我们不能).此外,如果您的邮件有附件,那么即使在磁盘上,您也无法触摸它们,直到消息被锁定为止.至于在线程中使用SendAsync.使用它的好处是您可以获得回调,然后您可以从中确定发送的结果.如果我们想要使用Send方法,我们需要将它包装在try ... catch块中,因为它失败了. (3认同)
  • @Eton如果你看一下我已经说明了userState变量的例子.它是一个在引发事件时传递给Callback方法的对象.在示例中,我将其用作唯一标识符,但基本上您可以将任意内容传递给它.不,它不是必需的,如果你不需要在回调方法中使用它,那么只需传入null. (2认同)
  • 这不起作用,因为消息在发送之前被丢弃.使用发送或在回调中处理消息.(请注意,msdn示例仅在发送被取消时才处理该消息.在支持它的文档中找不到任何内容) (2认同)

Jus*_*ner 12

简单地在单独的线程上发送消息并不复杂:

using System.Net.Mail;

Smtp.SendAsync(message);
Run Code Online (Sandbox Code Playgroud)

或者,如果您想在单独的线程上构造整个消息,而不是仅仅异步发送它:

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();
Run Code Online (Sandbox Code Playgroud)

  • 对于长时间运行的进程,也建议使用线程创建,而不是异步任务 (6认同)
  • [ObsoleteAttribute("建议的替代方案是System.Net.Mail.SmtpClient.http://go.microsoft.com/fwlink/?linkid=14202")] (5认同)

And*_*rey 8

SmtpClient.SendAsync方法

样品

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.");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @James去链接,那里有例子."示例[+]以下代码示例演示了如何调用此方法." (4认同)
  • -1 OP专门询问你是否只是要指定他们需要使用哪些方法,他们想要一个例子. (2认同)

Aug*_*eto 6

以下是使用.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);
});
Run Code Online (Sandbox Code Playgroud)

其中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
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

就像Azure App Services上的魅力一样.


fdf*_*rye 5

只是因为这有点模糊......我会简短的...

有很多方法可以在c#/ .net等中进行异步或并行工作.

做你想做的最快的方法是使用后台工作线程,这将避免锁定你的UI.

有后台工作线程的提示:你不能直接从它们更新UI(线程亲和力和编组只是你学会处理的东西......)

另一件需要考虑的事情......如果你使用标准的System.Net.Mail类型的东西来发送电子邮件......要小心你如何制作你的逻辑.如果你在某种方法中将它全部隔离并反复调用它,那么每次都可能必须拆除并重建与邮件服务器的连接,并且认证等所涉及的延迟仍然会不必要地减慢整个过程.在可能的情况下,通过单个打开的连接向邮件服务器发送多封电子邮件.