.SendMailAsync()在MVC中使用

时间:2013-11-21 19:15:25

标签: c# asp.net-mvc

我正在尝试从我的MVC应用程序发送电子邮件,它在我使用.Send()方法时发送正常,但需要一段时间才能回来,所以我想使用.SendMailAsync()函数,但我收到了执行期间出现以下错误。

  

此时无法启动异步操作。异步操作只能在异步处理程序或模块中启动,或者在页面生命周期中的某些事件中启动。如果在执行页面时发生此异常,请确保将页面标记为<%@ Page Async =“true”%>

这是我的代码示例。如何将其配置为使用.SendMailAsync()

发送

电子邮件包装类:

using System.Net.Mail;

namespace Helpers
{
    public class Email
    {
        // constants
        private const string HtmlEmailHeader = "<html><head><title></title></head><body style='font-family:arial; font-size:14px;'>";
        private const string HtmlEmailFooter = "</body></html>";

        // properties
        public List<string> To { get; set; }
        public List<string> CC { get; set; }
        public List<string> BCC { get; set; }
        public string From { get; set; }
        public string Subject { get; set; }
        public string Body { get; set; }  

        // constructor
        public Email()
        {
            To = new List<string>();
            CC = new List<string>();
            BCC = new List<string>();
        }

        // send
        public void Send()
        {
            MailMessage message = new MailMessage();

            foreach (var x in To)
            {
                message.To.Add(x);
            }
            foreach (var x in CC)
            {
                message.CC.Add(x);
            }
            foreach (var x in BCC)
            {
                message.Bcc.Add(x);
            }

            message.Subject = Subject;
            message.Body = string.Concat(HtmlEmailHeader, Body, HtmlEmailFooter);
            message.BodyEncoding = System.Text.Encoding.UTF8;
            message.From = new MailAddress(From);
            message.SubjectEncoding = System.Text.Encoding.UTF8;
            message.IsBodyHtml = true;

            SmtpClient client = new SmtpClient("relay.mail.server");

            client.SendMailAsync(message);            
        }
    }
}

控制器:

public ActionResult Index()
    {

        Email email = new Email();
        email.To.Add("to@email.com");
        email.From = "from@email.com";
        email.Subject = "Subject";
        email.Body = "<p><strong>Hello</strong></p><p>This is my first Email Message</p>";
        email.Send();
    }

修改

根据实际提出的问题,潜在的问题是发送电子邮件时产生的延迟。我在这篇文章的帮助下进一步研究了实际问题:

ASP.Net MVC background threads for email creation and sending

修改了我的Email Wrapper类以生成一个新线程来执行电子邮件处理:

using System.Net.Mail;

namespace Helpers
{
    public class Email
    {
        // constants
        private const string HtmlEmailHeader = "<html><head><title></title></head><body style='font-family:arial; font-size:14px;'>";
        private const string HtmlEmailFooter = "</body></html>";

        // properties
        public List<string> To { get; set; }
        public List<string> CC { get; set; }
        public List<string> BCC { get; set; }
        public string From { get; set; }
        public string Subject { get; set; }
        public string Body { get; set; }  

        // constructor
        public Email()
        {
            To = new List<string>();
            CC = new List<string>();
            BCC = new List<string>();
        }

        // send
        public void Send()
        {
            MailMessage message = new MailMessage();

            foreach (var x in To)
            {
                message.To.Add(x);
            }
            foreach (var x in CC)
            {
                message.CC.Add(x);
            }
            foreach (var x in BCC)
            {
                message.Bcc.Add(x);
            }

            message.Subject = Subject;
            message.Body = string.Concat(HtmlEmailHeader, Body, HtmlEmailFooter);
            message.BodyEncoding = System.Text.Encoding.UTF8;
            message.From = new MailAddress(From);
            message.SubjectEncoding = System.Text.Encoding.UTF8;
            message.IsBodyHtml = true;

            SmtpClient client = new SmtpClient("relay.mail.server");

            new Thread(() => { client.Send(message); }).Start();        
        }
    }
}

3 个答案:

答案 0 :(得分:66)

不可否认,错误有点迟钝,但它真正告诉你的是你从同步方法调用异步方法,这是不允许的。如果您要使用异步,则必须在链的整个过程中使用异步。

因此,首先您需要更改Send方法定义以返回Task

public async Task Send()

并将您的异步方法调用设置为等待:

await client.SendMailAsync(message);

然后,为您的行动做同样的事情:

public async Task<ActionResult> Index()

await email.Send();

<强>更新

Async不会按我认为的那样做。当您的操作被请求调用时,在操作中的所有代码完全执行之前,它不会返回响应。异步不是一个魔术棒,使动作更快地返回响应。您的任务(在这种情况下,发送电子邮件)需要花费的时间和异步与否,在任务完成之前,操作不会返回响应。

那么为什么要使用异步呢?因为async 所做的是从服务器池中释放线程。假设IIS在一个非常标准的配置中运行,你可能会有大约1000个线程可用。这通常称为“最大请求”,因为通常1个请求== 1个线程。因此,如果您的服务器负载很重并且您的部署超过“最大请求数”,则每个后续请求都会排队,直到来自池的线程再次可用。如果所有线程都在等待某些东西完成,那么你的服务器基本上就会死锁。但是,当你使用异步时,你告诉IIS本质上,“我正在等待一些东西。这是我的线程,所以你可以用它来记录另一个请求。我会在需要时通知你。”这允许队列中的请求继续进行。

长和短,当你做任何涉及等待的事情时,总是使用异步,因为它允许更有效地使用服务器资源,但请记住它不会使事情更快地发生。

EDIT 12/11/14 - 稍微更新了一些术语,以明确async仅在线程等待时才有用,而不仅仅涉及一些长时间运行任务。例如,运行复杂的财务计算可能“需要一段时间”,但不适合异步,因为所有工作都受CPU限制。该任务可能是长时间运行的,但是如果线程不处于等待状态,则它不能用于其他任务,并且您的异步方法基本上只是作为同步运行,但需要额外的开销。

答案 1 :(得分:4)

这可能会帮助你。

    public void Send(MailAddress toAddress, string subject, string body, bool priority)
    {
        Task.Factory.StartNew(() => SendEmail(toAddress, subject, body, priority), TaskCreationOptions.LongRunning);
    }

    private void SendEmail(MailAddress toAddress, string subject, string body, bool priority)
    {
        MailAddress fromAddress = new MailAddress(WebConfigurationManager.AppSettings["SmtpFromAddress"]);
        string serverName = WebConfigurationManager.AppSettings["SmtpServerName"];
        int port = Convert.ToInt32(WebConfigurationManager.AppSettings["SmtpPort"]);
        string userName = WebConfigurationManager.AppSettings["SmtpUserName"];
        string password = WebConfigurationManager.AppSettings["SmtpPassword"];

        var message = new MailMessage(fromAddress, toAddress);

        message.Subject = subject;
        message.Body = body;
        message.IsBodyHtml = true;
        message.HeadersEncoding = Encoding.UTF8;
        message.SubjectEncoding = Encoding.UTF8;
        message.BodyEncoding = Encoding.UTF8;
        if (priority) message.Priority = MailPriority.High;

        Thread.Sleep(1000);

        SmtpClient client = new SmtpClient(serverName, port);
            client.DeliveryMethod = SmtpDeliveryMethod.Network;
            client.EnableSsl = Convert.ToBoolean(WebConfigurationManager.AppSettings["SmtpSsl"]);
            client.UseDefaultCredentials = false;

            NetworkCredential smtpUserInfo = new NetworkCredential(userName, password);
            client.Credentials = smtpUserInfo;

            client.Send(message);

            client.Dispose();
            message.Dispose();
    }

Thread.Sleep在那里,因为这将发送邮件如此之快,以至于许多SMTP服务器将报告来自同一IP错误消息的太多电子邮件。虽然ASP.NET处理异步发送邮件,但它不会一次发送多条消息。在发送另一封电子邮件之前,它会等待回调。这种方法将以代码可以调用Send()的方式并行发送消息。

答案 2 :(得分:3)

我认为以下内容符合您的目标:

按如下方式修改您的控制器:

public async Task<ActionResult> Index()
{
    Email email = new Email();
    email.SendAsync();
}

在您的电子邮件类中添加SendAsync方法,如下所示

public async Task SendAsync()
{
    await Task.Run(() => this.send());
}

在发送电子邮件之前,操作将返回,并且不会阻止请求。

尝试使用以下代码查看默认mvc模板的行为:

[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
    LongRunningTaskAsync();
    return View();
}

public static async Task LongRunningTaskAsync()
{
    await Task.Run(() => LongRunningTask());
}

public static void LongRunningTask()
{
    Debug.WriteLine("LongRunningTask started");
    Thread.Sleep(10000);
    Debug.WriteLine("LongRunningTask completed");
}

登录页面将立即显示。但输出窗口将显示&#34; LongRunningTask已完成&#34; 10秒后。