4

嗨,我有一个 Outlook com 插件,它正在为我做一些简单的搜索技巧。我正在将它放在一起,但我遇到了内存不足的问题。这个过程非常简单,基本上循环通过一个outlook文件夹检查每个mailItem是否匹配。给定循环每次我都希望垃圾收集器跟上时重新初始化变量,但是当我观察内存时,它会丢失约 10 米/秒,直到系统内存不足并且我得到未处理的异常。

这是代码的一部分

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 由一个新线程调用,因此我可以在它继续搜索时与结果进行交互。

到目前为止我已经尝试过:

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

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

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

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

4

3 回答 3

4

首先:这是 NetOffice 代码,您不需要在 NetOffice 中使用 Marshal.ReleaseComObject。(此外,在这里调用 ReleaseComObject 是没有用的)请使用 Dispose() 代替实例。

请记住:NetOffice 为您处理 COM 代理(这就是为什么它允许在 NetOffice 中使用两个 2 点)。在您的情况下,其内部存储为: // FolderToSearch -- Items --Enumerator -- Item -- Item -- ....

在每个循环结束时使用 item.Dispose() 删除/释放项目实例或在 foreach 之后使用以下内容

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

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

Next: 这里的 Items 枚举器是一个自定义枚举器(由 NetOffice 提供),但是它适用于少量项目,但在更重的场景中不这样做(可能交换服务器和数千个项目)。本地工作站/程序无法在内存中处理此问题。为此,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);

这也应该有效。

于 2015-04-03T14:27:32.097 回答
3

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

  1. 用于System.Runtime.InteropServices.Marshal.ReleaseComObject()在完成 COM 对象时释放它们。这会强制对象上的 COM“释放”。
  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);
}

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

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

于 2015-03-18T03:10:38.440 回答
0

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

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

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

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

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

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

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

马特,你仍然没有释放代码中的所有对象。例如:

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

Folder 类的 Items 属性返回相应类的实例,该实例应在之后释放。我看到至少有两行代码增加了引用计数器。

于 2015-03-18T13:54:02.113 回答