Skip to content

Instantly share code, notes, and snippets.

@mattbenic
Last active November 22, 2018 03:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save mattbenic/400e3c039ab8ea3e33aa to your computer and use it in GitHub Desktop.
Save mattbenic/400e3c039ab8ea3e33aa to your computer and use it in GitHub Desktop.
Extension method to have SmtpClient's SendMailAsync respond to a CancellationToken
using System;
using System.Net.Mail;
using System.Threading;
using System.Threading.Tasks;
public static class SmtpClientExtensions
{
/// <summary>
/// Extension method to have SmtpClient's SendMailAsync respond to a CancellationToken
/// </summary>
public static async Task SendMailAsync(
this SmtpClient client,
MailMessage message,
CancellationToken token)
{
Action cancelSend = () =>
{
client.SendAsyncCancel();
};
using (var reg = token.Register(cancelSend))
{
await client.SendMailAsync(message);
}
}
}
@taspeotis
Copy link

I don't think this will work if the CancellationToken is already cancelled. CancellationToken.Register immediately and synchronously runs Register callbacks when the CancellationToken is already cancelled.

"If this token is already in the canceled state, the delegate will be run immediately and synchronously"

So ...Register(cancelSend) will immediately call client.SendAsyncCancel before SendMailAsync.

SendAsyncCancel is a no-op if no messages are in-flight.

I think you want something like:

var sendTask = client.SendMailAsync(message);

using (token.Register(cancelSend))
    await sendTask;

@mattbenic
Copy link
Author

Thanks, that's a good suggestion

@cwellsx
Copy link

cwellsx commented Feb 22, 2017

Does SendMailAsync cancel when SendAsyncCancel is called?

MSDN says "Use the SendAsyncCancel method to cancel a pending SendAsync operation" so I fear that SendAsyncCancel affects SendAsync but not SendMailAsync.

@RichardD2
Copy link

@cwellsx: Looking at the source, SendMailAsync just wraps SendAsync, so calling SendAsyncCancel will work.

@taspeotis
Copy link

Also for what it's worth SmtpClient is IDisposable and Dispose seems to tidy up any in-flight requests. So if you can instantiate your own SmtpClient (rather than taking an arbitrary one in to your extension method) you can avoid the race condition of calling Register(...SendAsyncCancel) with something like:

using (var smtpClient = new SmtpClient())
using (cancellationToken.Register(smtpClient.Dispose))
    await smtpClient.SendMailAsync("xxx@yyy.com", emailAddress, subject, body);

Although now you've got this weird double-dispose thing going on. (Which is harmless, but the method of calling SendAsyncCancel conveys the intent of what you're doing more clearly, in my opinion.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment