You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 08:19:50