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() functio
This might help you out.
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();
}
The Thread.Sleep is there because this will send mail through so fast that many SMTP servers will report too many emails from same IP error message. Although ASP.NET handles asynchronous send mail, it will not send more than one message at a time. It waits until callback occurs before sending another email. This approach will send messages in parallel as fast as the code can call Send().
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.
I think the below does what you're trying to accomplish:
Modify your controller as below:
public async Task<ActionResult> Index()
{
Email email = new Email();
email.SendAsync();
}
And in your Email class add the SendAsync method as below
public async Task SendAsync()
{
await Task.Run(() => this.send());
}
The action will return before the email is sent and the request will not be blocked.
Try to see the behaviour with default mvc template with this code:
[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");
}
The login page will be displayed instantly. But output window will display "LongRunningTask completed" 10 seconds later.