Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
210 views
in Technique[技术] by (71.8m points)

c# - .SendMailAsync() use in MVC

I am trying to send email from my MVC application, it sends fine when I use the .Send() method but takes a while to come back so I wanted to use the .SendMailAsync() function, but I am receiving the following error during execution.

An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the Page is marked <%@ Page Async="true" %>

This is my code sample. How can I configure this to send using .SendMailAsync()

Email Wrapper Class:

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

Controller:

public ActionResult Index()
    {

        Email email = new Email();
        email.To.Add("[email protected]");
        email.From = "[email protected]";
        email.Subject = "Subject";
        email.Body = "<p><strong>Hello</strong></p><p>This is my first Email Message</p>";
        email.Send();
    }

EDIT

Further to the actual question asked, the underlying issue was the delay created when sending emails. I looked further into the actual issue and with the help of this post:

ASP.Net MVC background threads for email creation and sending

modified my Email Wrapper class to spawn off a new thread to perform the email processing:

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();        
        }
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Admittedly, the error is a bit obtuse, but all it's really telling you is that you're calling an asynchronous method from a synchronous method, which isn't allowed. If you're going to use async, you have to use async all the way up the chain.

So, first you need to change your Send method definition to return a Task:

public async Task Send()

And set your async method call to await:

await client.SendMailAsync(message);

Then, do the same for your action:

public async Task<ActionResult> Index()

And:

await email.Send();

UPDATE

Async doesn't do what I think you think it does. When your action is invoked by a request, it will not return a response until all code inside the action has fully executed. Async is not a magic wand that makes the action return the response quicker. Your task (in this case, sending an email) takes as long as it takes and async or not, the action will not return a response until the task has completed.

So why use async then? Because what async does do is let go the thread from the server pool. Let's say IIS is running in a pretty standard config, you'll likely have somewhere around 1000 threads available. This is often called the "max requests", because typically 1 request == 1 thread. So, if you server comes under heavy load and you're fielding more than the "max requests", each subsequent request is queued until a thread from the pool becomes available again. If all the threads are tied up waiting on something to complete, then your server essentially deadlocks. But, when you use async, you tell IIS essentially, "I'm waiting on something. Here's my thread back, so you can use it to field another request. I'll let you know when I need it back." That allows requests in the queue to proceed.

Long and short, do always use async when you are doing anything that involves waiting, because it allows server resources to be used more efficiently, but remember that it doesn't make things happen quicker.

EDIT 12/11/14 - Updated terminology a bit to make clear that async is only useful when a thread is waiting, not just involved in some long-running task. For example, running complex financial calculations could "take a while", but would not be a good fit for async because all the work is CPU-bound. The task may be long-running, but if the thread is not in a wait-state, it can't be used for other tasks and your async method will essentially just run as sync, but with extra overhead.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...