SmtpClient.SendMailAsync:CancellationToken 支持






4.60/5 (3投票s)
为 SendMailAsync 方法添加取消支持
引言
自从 .NET 4.5 以来,SmtpClient 类 提供了 TPL 友好的 SendMailAsync 方法,以便更容易地从 async
方法异步发送电子邮件。不幸的是,没有任何重载支持传递 CancellationToken,这使得取消操作变得困难。
我最近遇到了这个缺点,在一个配置为在用户断开连接时取消的异步 ASP.NET MVC 操作中。如果这发生在发送电子邮件时,应用程序会记录一个带有消息的异常:“异步模块或处理程序在异步操作仍在挂起时完成”。这是因为该操作正在被取消,但发送电子邮件的任务没有被取消。
Using the Code
以下扩展方法添加了对传递 CancellationToken
的支持。当取消时,它将调用 SendAsyncCancel 方法 来取消操作。
using System;
using System.ComponentModel;
using System.Net.Mail;
using System.Threading;
using System.Threading.Tasks;
namespace System.Net.Mail
{
/// <summary>
/// Extension methods for the <see cref="SmtpClient"/> class.
/// </summary>
public static class SmtpClientExtensions
{
/// <summary>
/// Sends the specified message to an SMTP server for delivery as an asynchronous operation.
/// </summary>
/// <param name="client">
/// The <see cref="SmtpClient"/> instance.
/// </param>
/// <param name="message">
/// The <see cref="MailMessage"/> to send.
/// </param>
/// <param name="cancellationToken">
/// The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// </para>
/// <returns>
/// A <see cref="Task"/> representing the asynchronous operation.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="client"/> is <see langword="null"/>.</para>
/// <para>-or-</para>
/// <para><paramref name="message"/> is <see langword="null"/>.</para>
/// </exception>
public static Task SendMailAsync(
this SmtpClient client,
MailMessage message,
CancellationToken cancellationToken)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (message == null) throw new ArgumentNullException(nameof(message));
if (!cancellationToken.CanBeCanceled) return client.SendMailAsync(message);
var tcs = new TaskCompletionSource<object>();
var registration = default(CancellationTokenRegistration);
SendCompletedEventHandler handler = null;
handler = (sender, e) =>
{
if (e.UserState == tcs)
{
try
{
if (handler != null)
{
client.SendCompleted -= handler;
handler = null;
}
}
finally
{
registration.Dispose();
if (e.Error != null)
{
tcs.TrySetException(e.Error);
}
else if (e.Cancelled)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(null);
}
}
}
};
client.SendCompleted += handler;
try
{
client.SendAsync(message, tcs);
registration = cancellationToken.Register(client.SendAsyncCancel);
}
catch
{
client.SendCompleted -= handler;
registration.Dispose();
throw;
}
return tcs.Task;
}
}
}
使用它非常简单
using (var message = new MailMessage())
{
InitializeMessage(message);
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(2));
var smtp = new SmtpClient();
await smtp.SendMailAsync(message, cts.Token).ConfigureAwait(false);
}
关注点
网上有各种更简单的版本 - 例如,Matt Benic 在 2016 年发布的一个。但是,我希望代码尽可能接近 现有方法的源代码。
正如 Todd Aspeotis 在 Matt 版本评论中指出的那样,有必要在 CancellationToken
上注册回调之前调用 SendAsync
;否则,如果令牌已经取消,SendAsyncCancel
方法将被过早调用。
历史
- 2017-08-02:初始版本