C# SendMailAsync发送部分小图片附件时连接被强制关闭问题
使用SmtpClient的异步方法SendMailAsync发送带小图片附件的邮件时,会触发错误**"An existing connection was forcibly closed by the remote host"**,但其他大小的图片附件能正常发送;改用同步方法Send则可以成功发送这类有问题的附件。附件是基于Byte[]转换的带CID的MemoryStream,代码如下:
public async Task<bool> SendEmail(EmailAccount emailAccount, string[] toemails, string subject, string message, IEnumerable<Attachment> attachments = null) { bool succeed = false; try { using SmtpClient client = new(); client.DeliveryMethod = SmtpDeliveryMethod.Network; client.EnableSsl = true; client.Host = emailAccount.SMTPHost; client.Port = emailAccount.SMTPPort.Value; NetworkCredential credentials = new(emailAccount.UserName, emailAccount.UserPassword); client.UseDefaultCredentials = false; client.Credentials = credentials; using MailMessage msg = new(); msg.From = new MailAddress(emailAccount.UserName); foreach (string toemail in toemails) msg.To.Add(new MailAddress(toemail)); msg.Subject = subject; msg.IsBodyHtml = true; msg.Body = message; if (attachments != null) { foreach(var attachment in attachments) { msg.Attachments.Add(attachment); } } await client.SendMailAsync(msg); // 失效 //client.Send(msg); // 正常 Logger.LogInformation($"Email is sent successfully."); succeed = true; } catch (Exception ex) { Logger.LogCritical(ex, ex.Message); } return succeed; //return Task.FromResult<bool>(succeed); }
异步流处理的时序冲突:同步方法
Send会阻塞主线程,直到整个邮件构建、传输完全完成,MemoryStream和相关资源全程保持有效。但异步方法SendMailAsync是非阻塞的,代码里的MailMessage和SmtpClient都在using块中,要是异步传输还没读完流,using块就提前释放了资源(尤其是附件关联的MemoryStream),SMTP连接中途丢数据,远程服务器就会强制关闭连接。小文件传输速度快,这种时序矛盾更容易触发——大文件传输耗时久,反而大概率能在资源释放前完成传输。旧版SmtpClient异步实现的缺陷:.NET早期的
SendMailAsync没有完全遵循异步最佳实践,内部流管理逻辑和同步版本有差异,对短生命周期的流(比如MemoryStream)兼容性差。同步方法会全程持有流直到传输结束,而异步版本在后台线程处理时,流可能已经被外部的using或者GC回收了。附件流的生命周期未妥善保持:你添加的
Attachment关联的MemoryStream如果没有显式保留引用,异步场景下GC可能提前回收这部分内存。同步场景下主线程一直阻塞,GC不会在传输过程中回收这些资源,所以不会出问题。
内容的提问来源于stack exchange,提问作者jong shin

