Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple means of making a function non-blocking / async in C#?

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?

like image 702
SB2055 Avatar asked Dec 24 '22 22:12

SB2055


2 Answers

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.
like image 130
i3arnon Avatar answered Dec 29 '22 07:12

i3arnon


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) );
}
like image 34
nvoigt Avatar answered Dec 29 '22 05:12

nvoigt