Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cause of TaskCanceledException with SendMailAsync?

I don't understand the difference between these two implementations of calling SendMailAsync. Much of the time with the first, I'm receiving a TaskCanceledException, but with the second, everything works as expected.

I thought the two consuming methods would be equivalent, but clearly I'm missing something.

This seems related to, but opposite from: TaskCanceledException when not awaiting

// Common SmtpEmailGateway library
public class SmtpEmailGateway : IEmailGateway
{
    public Task SendEmailAsync(MailMessage mailMessage)
    {
        using (var smtpClient = new SmtpClient())
        {
            return smtpClient.SendMailAsync(mailMessage);
        }
    }
}

// Caller #1 code - Often throws TaskCanceledException
public async Task Caller1()
{
     // setup code here
     var smtpEmailGateway = new SmtpEmailGateway();
     await smtpEmailGateway.SendEmailAsync(myMailMessage);
}

// Caller #2 code - No problems
public Task Caller2()
{
     // setup code here
     var smtpEmailGateway = new SmtpEmailGateway();
     return smtpEmailGateway.SendEmailAsync(myMailMessage);
}

EDIT: It turns out the Caller2 method was also causing exceptions, I just wasn't seeing them due to the WebJobs framework this was being called by. Yuval's explanation cleared all of this up and is correct.

like image 928
Brian Vallelunga Avatar asked Nov 04 '15 14:11

Brian Vallelunga


Video Answer


1 Answers

The difference between your two method calls is that the former asynchronously waits using await when being invoked. Thus, the TaskCanceledException propagates from the inner SendEmailAsync call, which is caused by the fact that you're not awaiting the async method in the using scope, which causes a race condition between the disposal of SmtpClient and the end of the async call. While in the latter, the exception is being encapsulated inside the return Task object, which I'm not sure whether you're awaiting on or not. That's why in the former, you see the exception immediately.

The first thing that should be done is to properly await on SendEmailAsync inside the gateway:

public class SmtpEmailGateway : IEmailGateway
{
    public async Task SendEmailAsync(MailMessage mailMessage)
    {
        using (var smtpClient = new SmtpClient())
        {
            return await smtpClient.SendMailAsync(mailMessage);
        }
    }
}

Then, you can use the second method which avoids the overhead of creating the state-machine. The difference is now you're guaranteeing that SmtpClient will only dispose once the async operation completes.

like image 92
Yuval Itzchakov Avatar answered Oct 08 '22 17:10

Yuval Itzchakov