Skip to content

Commit

Permalink
Add SmtpClient.SendMailAsync overloads with cancellation
Browse files Browse the repository at this point in the history
  • Loading branch information
MihaZupan committed Nov 25, 2019
1 parent 3cf579e commit 3f9f0e7
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 9 deletions.
2 changes: 2 additions & 0 deletions src/libraries/System.Net.Mail/ref/System.Net.Mail.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ public void SendAsync(string from, string recipients, string subject, string bod
public void SendAsyncCancel() { }
public System.Threading.Tasks.Task SendMailAsync(System.Net.Mail.MailMessage message) { throw null; }
public System.Threading.Tasks.Task SendMailAsync(string from, string recipients, string subject, string body) { throw null; }
public System.Threading.Tasks.Task SendMailAsync(System.Net.Mail.MailMessage message, System.Threading.CancellationToken cancellationToken) { throw null; }
public System.Threading.Tasks.Task SendMailAsync(string from, string recipients, string subject, string body, System.Threading.CancellationToken cancellationToken) { throw null; }
}
public enum SmtpDeliveryFormat
{
Expand Down
61 changes: 52 additions & 9 deletions src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -786,17 +786,35 @@ public void SendAsyncCancel()
public Task SendMailAsync(string from, string recipients, string subject, string body)
{
var message = new MailMessage(from, recipients, subject, body);
return SendMailAsync(message);
return SendMailAsync(message, cancellationToken: default);
}

public Task SendMailAsync(MailMessage message)
{
return SendMailAsync(message, cancellationToken: default);
}

public Task SendMailAsync(string from, string recipients, string subject, string body, CancellationToken cancellationToken)
{
var message = new MailMessage(from, recipients, subject, body);
return SendMailAsync(message, cancellationToken);
}

public Task SendMailAsync(MailMessage message, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}

// Create a TaskCompletionSource to represent the operation
var tcs = new TaskCompletionSource<object>();
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);

CancellationTokenRegistration ctr = default;

// Register a handler that will transfer completion results to the TCS Task
SendCompletedEventHandler handler = null;
handler = (sender, e) => HandleCompletion(tcs, e, handler);
handler = (sender, e) => HandleCompletion(tcs, ctr, e, handler);
SendCompleted += handler;

// Start the async operation.
Expand All @@ -810,20 +828,45 @@ public Task SendMailAsync(MailMessage message)
throw;
}

// Only register on the CT if HandleCompletion hasn't started to ensure the CTR is disposed
bool lockTaken = false;
try
{
Monitor.TryEnter(tcs, ref lockTaken);
if (lockTaken && !tcs.Task.IsCompleted)
{
ctr = cancellationToken.Register(s =>
{
((SmtpClient)s).SendAsyncCancel();
}, this);
}
}
finally
{
if (lockTaken) Monitor.Exit(tcs);
}

// Return the task to represent the asynchronous operation
return tcs.Task;
}

private void HandleCompletion(TaskCompletionSource<object> tcs, AsyncCompletedEventArgs e, SendCompletedEventHandler handler)
private void HandleCompletion(TaskCompletionSource<object> tcs, CancellationTokenRegistration ctr, AsyncCompletedEventArgs e, SendCompletedEventHandler handler)
{
if (e.UserState == tcs)
{
try { SendCompleted -= handler; }
finally
lock (tcs)
{
if (e.Error != null) tcs.TrySetException(e.Error);
else if (e.Cancelled) tcs.TrySetCanceled();
else tcs.TrySetResult(null);
try
{
SendCompleted -= handler;
ctr.Dispose();
}
finally
{
if (e.Error != null) tcs.TrySetException(e.Error);
else if (e.Cancelled) tcs.TrySetCanceled();
else tcs.TrySetResult(null);
}
}
}
}
Expand Down

0 comments on commit 3f9f0e7

Please sign in to comment.