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

如何优化C# Interop.Outlook读取共享邮箱邮件代码以避免卡顿崩溃?

问题描述

使用C#的Interop.Outlook读取带有多文件夹的共享邮箱邮件时,出现程序卡顿、无数据返回的问题。日志显示程序执行到5. CreationTime > LastRun...阶段后停滞,该方法需重复执行200次以检查不同文件名的可用性。

相关IsAvailable方法代码:

private bool IsAvailable(ChartData data, out string avilableFileName)
{
    Log.Information("3. Checking if folders and files are Available");
    foreach (MAPIFolder folder in mAPIFolder.Folders)
    {
        if (folder.FullFolderPath.Contains(data.MailFolder))
        {
            var mailfolder = data.MailFolder;
            myfolder=outlookNameSpace.Folders["email"].
            Folders["Inbox"].Folders["reports"].Folders[mailfolder];
            break;
        }
    }

    Log.Information("4. Checking if Creation Time > Received Time");

    avilableFileName = string.Empty;
    Items mailItems = myfolder.Items;
    mailItems.Sort("ReceivedTime", true);
    foreach (Object item in mailItems)
    {
        if (item is MailItem)
        {
            MailItem mailItem = (MailItem)item;
            if (mailItem.CreationTime > data.LastRun)
            {
                Log.Information("5. CreationTime > LastRun...");
                foreach (var attachment in mailItem.Attachments)
                {
                    if (attachment is Attachment)
                    {
                        var attach = (Attachment)attachment;
                        string attachmentName = attach.FileName;
                        if (attachmentName.ToLower().Contains(data.Attachmentname.ToLower())                                       
                        && attachmentName.ToLower().EndsWith
                          (data.AttachmentExtension.ToLower()))
                        {
                            Log.Information($"6. Attachment Name: {attachmentName}");
                            avilableFileName = attachmentName;
                            return true;
                        }
                    }
                    
                }
            }
        }
    }
    return false;
}

停滞阶段的日志:

2023-04-25 17:52:30.829 +08:00 [INF] 5. CreationTime > LastRun...
2023-04-25 17:52:30.911 +08:00 [INF] 5. CreationTime > LastRun...
2023-04-25 17:52:31.013 +08:00 [INF] 5. CreationTime > LastRun...
2023-04-25 17:52:31.086 +08:00 [INF] 5. CreationTime > LastRun...
2023-04-25 17:52:31.157 +08:00 [INF] 5. CreationTime > LastRun...
2023-04-25 17:52:31.232 +08:00 [INF] 5. CreationTime > LastRun...
2023-04-25 17:52:31.343 +08:00 [INF] 5. CreationTime > LastRun...

优化与修复方案

1. 简化文件夹定位逻辑,避免冗余遍历

原代码先遍历所有文件夹,随后又通过层级路径重新获取目标文件夹,操作冗余且易出错。直接通过共享邮箱名称定位目标文件夹,减少IO开销:

// 替换原文件夹遍历代码
var sharedMailbox = outlookNameSpace.Folders["email"]; // 共享邮箱名称
myfolder = sharedMailbox.Folders["Inbox"].Folders["reports"].Folders[data.MailFolder];

// 可选:添加异常捕获,避免文件夹不存在导致崩溃
try
{
    myfolder = sharedMailbox.Folders["Inbox"].Folders["reports"].Folders[data.MailFolder];
}
catch (System.Runtime.InteropServices.COMException ex)
{
    Log.Error($"找不到目标文件夹 {data.MailFolder}: {ex.Message}");
    avilableFileName = string.Empty;
    return false;
}

2. 使用Restrict预过滤邮件,减少遍历量

原代码遍历所有邮件后再判断时间条件,效率极低。利用Outlook的Restrict方法提前筛选出符合CreationTime > data.LastRun的邮件:

// 替换原Items获取与排序逻辑
string timeFilter = $"[CreationTime] > '{data.LastRun.ToString("g")}'"; // 适配Outlook日期格式
Items filteredItems = myfolder.Items.Restrict(timeFilter);
filteredItems.Sort("ReceivedTime", true);

// 遍历过滤后的邮件
foreach (Object item in filteredItems)
{
    if (item is MailItem mailItem) // 模式匹配简化类型转换
    {
        Log.Information("5. CreationTime > LastRun...");
        // 附件处理逻辑...
    }
}

3. 优化附件匹配逻辑,减少重复计算

提前将目标附件名称和扩展名转为小写,避免每次匹配时重复调用ToLower();同时直接遍历Attachment类型,减少类型判断开销:

// 预处理目标匹配字符串
var targetName = data.Attachmentname.ToLower();
var targetExt = data.AttachmentExtension.ToLower();

// 替换原附件遍历代码
foreach (Attachment attach in mailItem.Attachments)
{
    string attachNameLower = attach.FileName.ToLower();
    if (attachNameLower.Contains(targetName) && attachNameLower.EndsWith(targetExt))
    {
        Log.Information($"6. Attachment Name: {attach.FileName}");
        avilableFileName = attach.FileName;
        return true;
    }
}

4. 手动释放COM对象,解决内存泄漏卡顿

Interop.Outlook的COM对象未正确释放会导致Outlook内存溢出、卡顿。添加释放方法并在使用后清理:

// 新增COM对象释放方法
private void ReleaseComObject(object obj)
{
    if (obj != null)
    {
        System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
        obj = null;
    }
}

// 在方法内使用后释放相关对象
// ...
ReleaseComObject(filteredItems);
ReleaseComObject(mailItem);
ReleaseComObject(attach);
ReleaseComObject(myfolder);

5. 批量处理替代200次重复调用

原方法需执行200次,改为一次性收集所有待检查的附件规则,批量遍历邮件匹配,减少重复的Outlook交互:

// 示例:提前收集所有需要检查的附件规则
List<(string Name, string Ext)> targetAttachments = new List<(string, string)>();
// 填充规则:targetAttachments.Add((data1.Attachmentname, data1.AttachmentExtension));

// 批量遍历匹配
foreach (MailItem mailItem in filteredItems)
{
    foreach (Attachment attach in mailItem.Attachments)
    {
        string attachNameLower = attach.FileName.ToLower();
        foreach (var target in targetAttachments)
        {
            string targetName = target.Name.ToLower();
            string targetExt = target.Ext.ToLower();
            if (attachNameLower.Contains(targetName) && attachNameLower.EndsWith(targetExt))
            {
                // 记录找到的文件,可根据需求存储对应的数据
                // ...
            }
        }
    }
    ReleaseComObject(mailItem);
}

6. 添加异常捕获,定位隐藏错误

原代码未处理COM异常,卡顿可能是因为未捕获的异常导致程序停滞。在方法内添加异常处理:

try
{
    // 原方法内的所有逻辑
}
catch (System.Runtime.InteropServices.COMException ex)
{
    Log.Error($"Outlook操作异常: {ex.Message}, 错误码: {ex.ErrorCode}");
    // 释放所有已创建的COM对象
    // ...
    avilableFileName = string.Empty;
    return false;
}
catch (Exception ex)
{
    Log.Error($"未知异常: {ex.Message}");
    avilableFileName = string.Empty;
    return false;
}

内容的提问来源于stack exchange,提问作者Shishank Jain

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 04:17:15