ASP.NET WebForms中SmtpClient.SendMailAsync随机抛出异步操作异常
ASP.NET WebForms中QueueBackgroundWorkItem内SendMailAsync随机抛SmtpException的解决办法
问题情况
旧版ASP.NET WebForms应用,在生产环境高负载时会随机出现调用SmtpClient.SendMailAsync抛出SmtpException的情况,错误提示是:
异步操作无法启动,需在异步处理程序/模块或页面生命周期特定事件中执行
所有邮件发送逻辑都已经放在System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem的异步委托里执行,代码示例如下:
System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(async () => { smtpClient = new SmtpClient(smtpSection.Host, smtpSection.Port); smtpClient.Timeout = 10000; smtpClient.Credentials = new System.Net.NetworkCredential(smtpSection.Username, smtpSection.Password); smtpClient.EnableSsl = smtpSection.UseSsl; ... await smtpClient.SendMailAsync(mail); });
试过加ConfigureAwait(false)、在异步函数开头设SynchronizationContext.SetSynchronizationContext(null),都没用。而且这个问题只在生产高负载时频发,调试或预发布环境基本碰不到。
问题根源
这是.NET Framework特有的同步上下文流转随机性问题:
- 高负载下,
QueueBackgroundWorkItem的异步委托执行时,ASP.NET的SynchronizationContext可能没被完全清除,导致await SendMailAsync之后意外回到了ASP.NET的同步上下文 SmtpClient.SendMailAsync底层有校验逻辑,一旦检测到ASP.NET同步上下文但又不在允许的页面生命周期或异步处理流程里,就会抛出那个异常- .NET 7及以上版本重构了同步上下文的处理逻辑,不存在这个问题
解决办法
办法1:用Task.Run包裹发送逻辑
把邮件发送的异步代码完全放进Task.Run里,强制让它在没有同步上下文的线程池线程上执行:
System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(async ct => { await Task.Run(async () => { using var smtpClient = new SmtpClient(smtpSection.Host, smtpSection.Port); smtpClient.Timeout = 10000; smtpClient.Credentials = new System.Net.NetworkCredential(smtpSection.Username, smtpSection.Password); smtpClient.EnableSsl = smtpSection.UseSsl; ... await smtpClient.SendMailAsync(mail, ct); }, ct); });
注意:加上CancellationToken参数,配合QueueBackgroundWorkItem的取消机制,防止任务无限制运行
办法2:换用MailKit库发送邮件
SmtpClient是.NET Framework里的旧类型,已经被标记为过时。换成MailKit这个更稳定的异步邮件库,它的异步实现不受ASP.NET同步上下文的影响:
System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(async ct => { using var client = new MailKit.Net.Smtp.SmtpClient(); await client.ConnectAsync(smtpSection.Host, smtpSection.Port, smtpSection.UseSsl, ct); await client.AuthenticateAsync(smtpSection.Username, smtpSection.Password, ct); await client.SendAsync(mailMessage, ct); await client.DisconnectAsync(true, ct); });
办法3:升级到.NET 7及以上版本
如果业务允许的话,把应用升级到.NET 7或更高版本,从根本上解决.NET Framework的同步上下文缺陷问题。
内容的提问来源于stack exchange,提问作者Geoduck
相关产品推荐
相关产品推荐

