gpt4 book ai didi

c# - SmtpClient.SendMailAsync 在抛出特定异常时导致死锁

转载 作者:可可西里 更新时间:2023-11-01 03:10:01 25 4
gpt4 key购买 nike

我正在尝试根据 VS2013 项目模板中的示例 AccountController 为 ASP.NET MVC5 网站设置电子邮件确认。我已经使用 SmtpClient 实现了 IIdentityMessageService,尽量保持简单:

public class EmailService : IIdentityMessageService
{
public async Task SendAsync(IdentityMessage message)
{
using(var client = new SmtpClient())
{
var mailMessage = new MailMessage("some.guy@company.com", message.Destination, message.Subject, message.Body);
await client.SendMailAsync(mailMessage);
}
}
}

调用它的 Controller 代码直接来自模板(提取到一个单独的操作中,因为我想排除其他可能的原因):

public async Task<ActionResult> TestAsyncEmail()
{
Guid userId = User.Identity.GetUserId();

string code = await UserManager.GenerateEmailConfirmationTokenAsync(userId);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = userId, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(userId, "Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");

return View();
}

但是,当邮件发送失败时,我会出现奇怪的行为,但仅在一个特定的实例中,当主机以某种方式无法访问时。示例配置:

<system.net>
<mailSettings>
<smtp deliveryMethod="Network">
<network host="unreachablehost" defaultCredentials="true" port="25" />
</smtp>
</mailSettings>
</system.net>

在那种情况下,请求会出现死锁,永远不会向客户端返回任何内容。如果由于任何其他原因(例如主机主动拒绝连接)邮件发送失败,异常会正常处理,我会收到 YSOD。

查看 Windows 事件日志,似乎在同一时间范围内抛出 InvalidOperationException,并显示消息“异步模块或处理程序已完成,而异步操作仍在挂起。”;如果我尝试在 Controller 中捕获 SmtpException 并在 catch block 中返回 ViewResult,我会在 YSOD 中收到相同的消息。所以我认为 await-ed 操作在任何一种情况下都无法完成。

据我所知,我正在遵循 SO 上其他帖子(例如 HttpClient.GetAsync(...) never returns when using await/async)中概述的所有 async/await 最佳实践,主要是“一直使用 async/await”。我也尝试过使用 ConfigureAwait(false),但没有任何变化。由于代码只有在抛出特定异常时才会死锁,所以我认为一般模式在大多数情况下都是正确的,但内部发生的某些事情使其在这种情况下不正确;但由于我对并发编程还很陌生,所以我觉得我可能是错的。

我做错了什么吗?我始终可以在 SendAsync 方法中使用同步调用(即 SmtpClient.Send()),但感觉应该按原样工作。

最佳答案

试试这个实现,只需使用 client.SendMailExAsync 而不是 client.SendMailAsync。让我们知道它是否有任何不同:

public static class SendMailEx
{
public static Task SendMailExAsync(
this System.Net.Mail.SmtpClient @this,
System.Net.Mail.MailMessage message,
CancellationToken token = default(CancellationToken))
{
// use Task.Run to negate SynchronizationContext
return Task.Run(() => SendMailExImplAsync(@this, message, token));
}

private static async Task SendMailExImplAsync(
System.Net.Mail.SmtpClient client,
System.Net.Mail.MailMessage message,
CancellationToken token)
{
token.ThrowIfCancellationRequested();

var tcs = new TaskCompletionSource<bool>();
System.Net.Mail.SendCompletedEventHandler handler = null;
Action unsubscribe = () => client.SendCompleted -= handler;

handler = async (s, e) =>
{
unsubscribe();

// a hack to complete the handler asynchronously
await Task.Yield();

if (e.UserState != tcs)
tcs.TrySetException(new InvalidOperationException("Unexpected UserState"));
else if (e.Cancelled)
tcs.TrySetCanceled();
else if (e.Error != null)
tcs.TrySetException(e.Error);
else
tcs.TrySetResult(true);
};

client.SendCompleted += handler;
try
{
client.SendAsync(message, tcs);
using (token.Register(() => client.SendAsyncCancel(), useSynchronizationContext: false))
{
await tcs.Task;
}
}
finally
{
unsubscribe();
}
}
}

关于c# - SmtpClient.SendMailAsync 在抛出特定异常时导致死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28333396/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com