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("noreply@me.com", "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?

i3arnon
  • 113,022
  • 33
  • 324
  • 344
SB2055
  • 12,272
  • 32
  • 97
  • 202
  • Don't use `Task.Run` when you have an `async` version of the operation. That wastes threads and limits scalability. look at [my answer](http://stackoverflow.com/a/28536199/885318) – i3arnon Feb 16 '15 at 07:59
  • @SB2055, ideally, you should also support cancellation, something `SmtpClient.SendMailAsync` doesn't support. You may want to look at `SendMailExImplAsync` from [here](http://stackoverflow.com/a/28445791/1768303). – noseratio Feb 16 '15 at 21:44

2 Answers2

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("noreply@me.com", "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.
i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • 1
    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 Feb 16 '15 at 08:21
  • 1
    @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. – i3arnon Feb 16 '15 at 09:53
  • 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 Feb 17 '15 at 06:47
  • 1
    @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` – i3arnon Feb 17 '15 at 06:55
  • From my point of view, anything after the await in the function that he may want to have is blocked. But this is a democracy, if enough people vote, I will simply delete my answer and switch to another question. – nvoigt Feb 17 '15 at 07:02
  • @nvoigt That's not the meaning of blocking, nor does it seem to be the meaning the OP is referring to since the goal is to "send the email in the background without holding up the UI as the email is processed". Even if it was, there was still no reason to not use `await`, you can just store the task and await it as the last line in the event handler (which is the case now as it's empty). Regardless of await using `SmtpClient.SendMailAsync` is better than `SmptClient.Send` performance-wise. – i3arnon Feb 17 '15 at 07:27
  • 1
    @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. – i3arnon Feb 17 '15 at 07:29
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) );
}
nvoigt
  • 75,013
  • 26
  • 93
  • 142
  • 4
    No need for `Task.Run`. `SmtpClient` has a naturally asynchronous API. – Yuval Itzchakov Feb 16 '15 at 08:19
  • 1
    `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. – Cameron Feb 16 '15 at 21:43
  • 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 Feb 17 '15 at 06:51
  • 1
    @nvoigt The other answer doesn't block (which is the point of awaiting something). See [this comment](http://stackoverflow.com/questions/28536092/simple-means-of-making-a-function-non-blocking-async-in-c/28536199?noredirect=1#comment45424662_28536199) – i3arnon Feb 17 '15 at 06:56