4

I have a button that - when the user clicks it - sends an email. I'd love for this to just return immediately and send the email in the background without holding up the UI as the email is processed. I've searched around for async/await/etc and I've found so many different approaches - I'm looking for a simple solution to this. My email code:

public void SendEmail(string toAddress, string subject, string body, string code = null) {
    try {

        var fromAddressObj = new MailAddress("[email protected]", "Name");
        var toAddressObj = new MailAddress(toAddress, toAddress);
        const string fromPassword = "Password";

        var smtp = new SmtpClient {
            Host = "smtp.office365.com",
            Port = 587,
            EnableSsl = true,
            DeliveryMethod = SmtpDeliveryMethod.Network,
            UseDefaultCredentials = false,
            Credentials = new NetworkCredential(fromAddressObj.Address, fromPassword)
        };
        using (var message = new MailMessage(fromAddressObj, toAddressObj) {
            Subject = subject,
            IsBodyHtml = true

        }) {
            message.Body = body;
            smtp.Send(message);
        }

    } catch (Exception e) {
        Elmah.ErrorSignal.FromCurrentContext().Raise(e);
    }
}

How can I modify this so that the caller is not blocked?

2
  • Don't use Task.Run when you have an async version of the operation. That wastes threads and limits scalability. look at my answer Commented Feb 16, 2015 at 7:59
  • @SB2055, ideally, you should also support cancellation, something SmtpClient.SendMailAsync doesn't support. You may want to look at SendMailExImplAsync from here. Commented Feb 16, 2015 at 21:44

2 Answers 2

6

SmtpClient has SendMailAsyncso using it instead of Send will unblock the UI thread while sending but you can still handle any exception that may occur:

private async void button_Click(object sender, EventArgs e)
{
   await SendEmailAsync(address, subject, body)
}

public async Task SendEmailAsync(string toAddress, string subject, string body, string code = null) 
{
    try 
    {
        var fromAddressObj = new MailAddress("[email protected]", "Name");
        var toAddressObj = new MailAddress(toAddress, toAddress);
        const string fromPassword = "Password";

        var smtp = new SmtpClient {
            Host = "smtp.office365.com",
            Port = 587,
            EnableSsl = true,
            DeliveryMethod = SmtpDeliveryMethod.Network,
            UseDefaultCredentials = false,
            Credentials = new NetworkCredential(fromAddressObj.Address, fromPassword)
        };
        using (var message = new MailMessage(fromAddressObj, toAddressObj)
        {
            Subject = subject,
            IsBodyHtml = true
        }) 
        {
            message.Body = body;
            await smtp.SendMailAsync(message);
        }
    }
    catch (Exception e) 
    {
        Elmah.ErrorSignal.FromCurrentContext().Raise(e);
    }
}

If the synchronous part of the async method (the part before the await) still manages to block the UI thread you can offload it to a ThreadPool thread:

private async void button_Click(object sender, EventArgs e)
{
   await Task.Run(() => SendEmailAsync(address, subject, body))
}

Notes:

  • Don't fire up a Task without awaiting it unless you're in a situation where you can't use await (a UI event handler is not one of those cases)
  • async void is only appropriate for UI event handlers. Don't use it anywhere else.
Sign up to request clarification or add additional context in comments.

7 Comments

Would you mind explaining why the selected answer is wrong? Suppose there wasn't an Async version of the operation and I still needed to run it without blocking - then I obviously couldn't use await, right? So it seems that in some cases, using Task without await is desirable? Apologies for my naivete, any explanation would help a ton.
@SB2055 If there wasn't an asynchronous version (async or other ones) then Task.Run would be very appropriate. But You would still have a Task to await which is the task the Task.Run returns. You need a good reason to not await a Task and there isn't one in your case.
I find it pretty preposterous to tell the OP what is necessary for him. He decides what he deems neccessary. If he does not want to wait for the function, telling him that he must do so is not an answer to his question. Your answer is very good technically... but it's not an answer to this question.
@nvoigt No, what's necessary for the OP is that the operation would be: "non-blocking" and "return immediately and send the email in the background without holding up the UI as the email is processed". This is achieved by simply using async-await. There's no reason not to await the returned Task and it's bad to do so. If for some reason the OP really couldn't use await then at least add a continuation handling any issues using ContinueWith
@nvoigt This is not a democracy and you shouldn't delete your answer because it got downvoted just as I didn't delete my answer because yours was upvoted and accepted or because you downvoted it. You should keep you answer if you think it's correct. You should delete it when you realize it's wrong.
|
5

You could run your method in a task and not wait for it:

private void button_Click(object sender, EventArgs e)
{
   Task.Run( () => SendEMail(address, subject, body) );
}

4 Comments

No need for Task.Run. SmtpClient has a naturally asynchronous API.
Task.Run is for CPU bound operations. This is an IO bound operation. As @YuvalItzchakov mentioned, SmtpClient already has a SendMailAsync operation. While this works, it's not the best solution.
How about you make your own answer and suggest a version, where you don't block the UI and it can immediately continue? The other answer here does not satisfy the OPs request of "caller is not blocked". It does block the further execution of statements by await-ing. That's the point of awaiting something.
@nvoigt The other answer doesn't block (which is the point of awaiting something). See this comment

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.