使用Microsoft Graph发送带大小附件的邮件时程序无限运行无报错
问题分析与解决方案
核心问题:同步阻塞引发的死锁
在.NET 4.7.2中,若在带有同步上下文的线程(如UI线程)里使用.GetAwaiter().GetResult()阻塞等待异步操作完成,极易触发死锁。原因是异步方法会捕获当前同步上下文,而同步阻塞会占用该上下文,导致异步回调无法执行,最终程序陷入无限等待状态。
其他潜在问题
- 文件流位置未重置:打开大附件文件流后,流指针默认停留在文件末尾,直接上传会导致无数据传输,可能引发上传停滞。
- 小附件ContentBytes设置不规范:Graph SDK 5中
FileAttachment提供了直接的ContentBytes属性,无需通过AdditionalData字典间接设置。 - ContentType匹配错误:固定使用
application/octet-stream不够规范,建议根据文件实际类型设置对应MIME类型。
修正后的代码
方案1:异步到底(推荐)
将方法改为异步模式,全程使用await避免阻塞,这是.NET异步编程的最佳实践:
public async Task<string> SendEmailWithAttachmentsAsync(GraphServiceClient graphClient, string objectID, string recipient, FileInfo littleAttachment, FileInfo bigAttachment) { var _Msg = string.Empty; // 创建邮件草稿 var draftMessage = new Microsoft.Graph.Models.Message { Subject = "Large attachment test", ToRecipients = new List<Recipient> { new Recipient { EmailAddress = new EmailAddress { Address = recipient } } } }; // 添加小附件 byte[] littleStream = File.ReadAllBytes(littleAttachment.FullName); draftMessage.Attachments = new List<Microsoft.Graph.Models.Attachment> { new FileAttachment { OdataType = "#microsoft.graph.fileAttachment", Name = littleAttachment.Name, ContentBytes = littleStream } }; // 保存草稿 var savedDraft = await graphClient.Users[objectID].Messages.PostAsync(draftMessage); // 处理大附件上传 using (var fileStream = System.IO.File.OpenRead(bigAttachment.FullName)) { // 重置流指针到文件开头 fileStream.Seek(0, SeekOrigin.Begin); var uploadRequestBody = new Microsoft.Graph.Users.Item.Messages.Item.Attachments.CreateUploadSession.CreateUploadSessionPostRequestBody { AttachmentItem = new AttachmentItem { AttachmentType = AttachmentType.File, Name = bigAttachment.Name, Size = fileStream.Length, // 自动获取文件对应MIME类型,需引用System.Web程序集 ContentType = MimeMapping.GetMimeMapping(bigAttachment.FullName) } }; var uploadSession = await graphClient.Users[objectID] .Messages[savedDraft.Id] .Attachments .CreateUploadSession .PostAsync(uploadRequestBody); // 最大分片大小必须是320KiB的倍数 int maxSliceSize = 320 * 1024; var fileUploadTask = new LargeFileUploadTask<FileAttachment>(uploadSession, fileStream, maxSliceSize); var totalLength = fileStream.Length; IProgress<long> progress = new Progress<long>(prog => { _Msg = $"Uploaded {prog} bytes of {totalLength} bytes"; }); try { var uploadResult = await fileUploadTask.UploadAsync(progress); _Msg = uploadResult.UploadSucceeded ? "Upload complete" : "Upload failed"; } catch (ServiceException ex) { _Msg = $"Error uploading: {ex.ToString()}"; } } // 发送草稿 await graphClient.Users[objectID].Messages[savedDraft.Id].Send.PostAsync(); return _Msg + " send: succeed"; }
方案2:强制同步调用(仅当无法改为异步时使用)
若必须保持同步方法,需在每个异步调用后添加.ConfigureAwait(false)避免捕获同步上下文,同时修正流位置:
public string SendEmailWithAttachments(GraphServiceClient graphClient, string objectID, string recipient, FileInfo littleAttachment, FileInfo bigAttachment) { var _Msg = string.Empty; var draftMessage = new Microsoft.Graph.Models.Message { Subject = "Large attachment test", ToRecipients = new List<Recipient> { new Recipient { EmailAddress = new EmailAddress { Address = recipient } } } }; byte[] littleStream = File.ReadAllBytes(littleAttachment.FullName); draftMessage.Attachments = new List<Microsoft.Graph.Models.Attachment> { new FileAttachment { OdataType = "#microsoft.graph.fileAttachment", Name = littleAttachment.Name, ContentBytes = littleStream } }; // 避免捕获同步上下文,防止死锁 var savedDraft = graphClient.Users[objectID].Messages.PostAsync(draftMessage).ConfigureAwait(false).GetAwaiter().GetResult(); using (var fileStream = System.IO.File.OpenRead(bigAttachment.FullName)) { fileStream.Seek(0, SeekOrigin.Begin); var uploadRequestBody = new Microsoft.Graph.Users.Item.Messages.Item.Attachments.CreateUploadSession.CreateUploadSessionPostRequestBody { AttachmentItem = new AttachmentItem { AttachmentType = AttachmentType.File, Name = bigAttachment.Name, Size = fileStream.Length, ContentType = MimeMapping.GetMimeMapping(bigAttachment.FullName) } }; var uploadSession = graphClient.Users[objectID] .Messages[savedDraft.Id] .Attachments .CreateUploadSession .PostAsync(uploadRequestBody) .ConfigureAwait(false) .GetAwaiter() .GetResult(); int maxSliceSize = 320 * 1024; var fileUploadTask = new LargeFileUploadTask<FileAttachment>(uploadSession, fileStream, maxSliceSize); var totalLength = fileStream.Length; IProgress<long> progress = new Progress<long>(prog => { _Msg = $"Uploaded {prog} bytes of {totalLength} bytes"; }); try { var uploadResult = fileUploadTask.UploadAsync(progress).ConfigureAwait(false).GetAwaiter().GetResult(); _Msg = uploadResult.UploadSucceeded ? "Upload complete" : "Upload failed"; } catch (ServiceException ex) { _Msg = $"Error uploading: {ex.ToString()}"; } } graphClient.Users[objectID].Messages[savedDraft.Id].Send.PostAsync().ConfigureAwait(false).GetAwaiter().GetResult(); return _Msg + " send: succeed"; }
关键说明
- 异步优先:方案1的异步写法能彻底避免死锁问题,同时提升程序响应性,是.NET官方推荐的写法。
- 流管理:使用
using语句确保文件流被正确释放,上传前重置流指针到起始位置,避免无数据上传。 - ContentType优化:使用
MimeMapping.GetMimeMapping自动匹配文件MIME类型,若无法引用System.Web,可使用第三方库如MimeKit替代。
内容的提问来源于stack exchange,提问作者CDVS
相关产品推荐
相关产品推荐

