运行 循环遍历邮件项目时内存不足

Running out of memory looping through mail items

您好,我有一个 Outlook com 加载项,可以为我执行一些简单的搜索技巧。我正在将它放在一起,但我遇到了问题 运行 内存不足。该过程非常简单,基本上循环遍历 outlook 文件夹,检查每个 mailItem 是否匹配。考虑到每次循环都重新初始化变量,我希望垃圾收集器能够跟上,但是当我看到内存时,它会丢失 ~10m/sec 直到系统内存不足并且我得到未处理的异常。

这是代码的一部分

private void FindInFolder(Outlook.MAPIFolder FolderToSearch)
    {
        Outlook.MailItem mailItem;
        Outlook.MAPIFolder ParentFolder;

        int counter = 0;

        StatusBar.Text = "Searching in Folder " + FolderToSearch.FolderPath + "/" + FolderToSearch.Name;
        StatusBar.Update();
        this.Update();

        foreach (COMObject item in FolderToSearch.Items)
        {
            counter++;
            if (counter % 100 == 0)
            {
                StatusBar.Text = FolderToSearch.FolderPath + "/" + FolderToSearch.Name + " item " + counter + " of " + FolderToSearch.Items.Count;
                StatusBar.Update();
                if (counter % 1000 == 0)
                {
                    GC.Collect();
                }
            }
            if (item is Outlook.MailItem)
            {
                mailItem = item as Outlook.MailItem;
                if (IsMatch(mailItem))
                {
                    if (mailItem.Parent is Outlook.MAPIFolder)
                    {
                            ParentFolder = mailItem.Parent as Outlook.MAPIFolder;
                            ResultGrd.Rows.Add(mailItem.EntryID, ParentFolder.FolderPath, mailItem.SenderName, mailItem.Subject, mailItem.SentOn);
                    }
                }
            }
            mailItem = null;
        }
    }

调用

        private Boolean IsMatch(Outlook.MailItem inItem)
    {
        Boolean subBool = false;
        Boolean NameBool = false;

        try
        {
            if (null != inItem)
            {
                if (SubjectTxt.Text != "")
                {
                    if (inItem.Subject.Contains(SubjectTxt.Text))
                    {
                        subBool = true;
                    }
                }
                else
                {
                    subBool = true;                    
                }

                if (NameTxt.Text != "")
                {
                    if (inItem.Sender != null)
                    {
                        if (inItem.Sender.Name.Contains(NameTxt.Text))
                        {
                            NameBool = true;
                        }
                    }
                }
                else 
                {
                    NameBool = true;
                }

                return subBool && NameBool;

            }
        }
        catch (System.Runtime.InteropServices.COMException ce)
        {
            if (ce.ErrorCode == -2147467259)
            {
                //DO nothing just move to the next one
            }
            else
            {
                MessageBox.Show("Crash in IsMatch error code = " + ce.ErrorCode + " " + ce.InnerException);
            }
        }
        return false;
    }

请原谅底部的所有错误捕获部分和 GC.collect 它们是我尝试找出错误并释放内存的一些尝试。

另请注意,新线程会调用 FindInFolder,因此我可以在它继续搜索的同时与结果进行交互。

到目前为止我尝试过的:

使变量局部于函数而不是 class 因此 G 可以检索它们,但是 'item' 中最常用的变量,因为它是 foreach 的一部分,所以必须以这种方式声明。

每 1000 个 mailItems 执行一次手动 GC,这根本没有区别。

出于某种原因,它需要大量内存来循环遍历项目,而 GC 永远不会释放它们。

另请注意,我对 Com 插件使用的是 netoffice 而不是 VSTO。

在使用 C# 中的 COM 对象时,我使用了 2 个技巧来防止内存和 COM 对象引用计数的建立:

  1. 使用 System.Runtime.InteropServices.Marshal.ReleaseComObject() 释放 COM 对象。这会在对象上强制使用 COM "release"。
  2. 不要foreach 遍历 COM 集合。 foreach 保留枚举器对象,以防止其他对象被释放。

所以,而不是这个:

foreach (COMObject item in FolderToSearch.Items)
{
    // ....
}

这样做:

Items items = FolderToSearch.Items;
try
{
    for (int i = 0; i < items.Count; ++i)
    {
        COMObject item = items[i];
        try
        {
            // work
        }
        finally
        {
            System.Runtime.InteropServices.Marshal.ReleaseComObject(item);
        }
    }
}
finally
{
    System.Runtime.InteropServices.Marshal.ReleaseComObject(items);
}

这些技巧帮助我减少了内存和对象消耗。

免责声明:虽然我无法证明这是否是好的做法。

首先,我建议使用项目 class 的 Find/FindNext or Restrict 方法,而不是遍历文件夹中的所有项目。例如:

Sub DemoFindNext() 
 Dim myNameSpace As Outlook.NameSpace 
 Dim tdystart As Date 
 Dim tdyend As Date 
 Dim myAppointments As Outlook.Items 
 Dim currentAppointment As Outlook.AppointmentItem 

 Set myNameSpace = Application.GetNamespace("MAPI") 
 tdystart = VBA.Format(Now, "Short Date") 
 tdyend = VBA.Format(Now + 1, "Short Date") 
 Set myAppointments = myNameSpace.GetDefaultFolder(olFolderCalendar).Items 
 Set currentAppointment = myAppointments.Find("[Start] >= """ & tdystart & """ and [Start] <= """ & tdyend & """") 
 While TypeName(currentAppointment) <> "Nothing" 
   MsgBox currentAppointment.Subject 
   Set currentAppointment = myAppointments.FindNext 
 Wend 
End Sub

有关详细信息和示例代码,请参阅以下文章:

此外,您可能会发现应用程序 class 的 AdvancedSearch 方法很有帮助。下面列出了使用 AdvancedSearch 方法的主要好处:

  • 搜索在另一个线程中执行。您不需要手动 运行 另一个线程,因为 AdvancedSearch 方法 运行 会自动在后台运行它。
  • 可以在任何位置(即超出某个文件夹的范围)搜索任何项目类型:邮件、约会、日历、便笺等。 Restrict 和 Find/FindNext 方法可以应用于特定的项目集合(请参阅 Outlook 中文件夹 class 的项目 属性)。
  • 完全支持 DASL 查询(自定义属性也可用于搜索)。您可以在 MSDN 的过滤文章中阅读更多相关信息。为了提高搜索性能,如果为商店启用了即时搜索,则可以使用即时搜索关键字(请参阅商店 class 的 IsInstantSearchEnabled 属性)。
  • 您可以随时使用搜索的停止方法停止搜索过程 class。

其次,我总是建议立即释放底层 COM 对象。使用完 Outlook 对象后,使用 System.Runtime.InteropServices.Marshal.ReleaseComObject 释放它。然后在 Visual Basic 中将变量设置为 Nothing(在 C# 中为 null)以释放对该对象的引用。您可以在 Systematically Releasing Objects 文章中阅读更多相关信息。

如果要使用GC,需要调用两次Collect和WaitForPendingFinalizers方法。

Matt,您仍然没有释放代码中的所有对象。例如:

for (int i = 0; i < FolderToSearch.Items.Count; ++i)
{
   COMObject item = FolderToSearch.Items[i];

文件夹class的项目属性 returns对应class的一个实例,应该在之后释放。我至少看到两行代码增加了引用计数器。

首先:这是 NetOffice 代码,您在 NetOffice 中不需要 Marshal.ReleaseComObject。 (而且,这里调用ReleaseComObject也没用)实例改用Dispose()

请记住:NetOffice 为您处理 COM 代理(这就是为什么它允许在 NetOffice 中使用两个 2 点)。 在您的情况下,其内部存储为: // 文件夹搜索 - 项目 --枚举器 - 物品 - 物品 -- ....

在每个循环结束时使用 item.Dispose() 到 remove/free 项目实例或在 foreach

之后使用以下内容

FolderToSearch.Dipose() // 处理文件夹实例和那里的所有代理来自

FolderToSearch.DisposeChildInstances() // 处理来自那里的所有代理,但保持文件夹实例处于活动状态

下一个: 这里的 Items 枚举器是一个自定义的枚举器(由 NetOffice 提供) 然而它适用于少量物品但不要更多地这样做 较重的场景(可能交换服务器和数千个项目)。本地 workstation/program 无法在内存中处理此问题。为此,Microsoft 仅提供众所周知的 GetFirst/GetNext 模式。在 NetOffice 中它看起来像:

Outlook._Items items = FolderToSearch.Items;
COMObject item = null;
do
{
    if (null == item)
       item = items.GetFirst() as COMObject;
    if (null == item)
       break;

    // do what you want here

    item.Dispose();
    item = items.GetNext() as COMObject;
} while (null != item);

这应该也有效。