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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With