.SendMailAsync()在MVC中使用

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

此时无法启动异步操作。 异步操作只能在异步处理程序或模块中启动,或者在页面生命周期中的某些事件中启动。 如果在执行页面时发生此exception,请确保将页面标记为

这是我的代码示例。 如何配置此命令以使用.SendMailAsync()发送

电子邮件包装类:

using System.Net.Mail; namespace Helpers { public class Email { // constants private const string HtmlEmailHeader = ""; private const string HtmlEmailFooter = ""; // properties public List To { get; set; } public List CC { get; set; } public List BCC { get; set; } public string From { get; set; } public string Subject { get; set; } public string Body { get; set; } // constructor public Email() { To = new List(); CC = new List(); BCC = new List(); } // 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 = "

Hello

This is my first Email Message

"; email.Send(); }

编辑

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

用于电子邮件创建和发送的ASP.Net MVC后台线程

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

 using System.Net.Mail; namespace Helpers { public class Email { // constants private const string HtmlEmailHeader = ""; private const string HtmlEmailFooter = ""; // properties public List To { get; set; } public List CC { get; set; } public List BCC { get; set; } public string From { get; set; } public string Subject { get; set; } public string Body { get; set; } // constructor public Email() { To = new List(); CC = new List(); BCC = new List(); } // 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(); } } } 

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

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

 public async Task Send() 

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

 await client.SendMailAsync(message); 

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

 public async Task Index() 

和:

 await email.Send(); 

UPDATE

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

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

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

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

这可能会帮助你。

  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()的速度并行发送消息。

我认为以下是你想要完成的事情:

修改您的控制器如下:

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

在您的Email类中添加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"); } 

登录页面将立即显示。 但输出窗口将在10秒后显示“LongRunningTask completed”。

使用评论中提到的此方法可以延长总体交付时间,但我认为OP正在尝试立即返回操作并在后台发送电子邮件。