耗尽内存循环通过邮件项目

时间:2015-03-18 02:51:57

标签: c# garbage-collection outlook-addin netoffice

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

到目前为止我尝试过:

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

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

出于某种原因,它需要很多内存才能循环遍历项目,而GC从不会释放它们。

请注意我使用的是netoffice而不是VSTO for Com addin。

3 个答案:

答案 0 :(得分:4)

首先:这是NetOffice代码,你不需要NetOffice中的Marshal.ReleaseComObject。 (此外,在此处调用ReleaseComObject无用)代替实例使用Dispose()。

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

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

FolderToSearch.Dipose() // dispose文件夹实例和所有代理来自

FolderToSearch.DisposeChildInstances() //配置所有代理来自但保持文件夹实例活着

下一步: 这里的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);

这也应该有效。

答案 1 :(得分:3)

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

  1. 使用System.Runtime.InteropServices.Marshal.ReleaseComObject()释放COM对象。这迫使COM"释放"在对象上。
  2. 不要foreach遍历COM集合。 foreach保留一个枚举器对象,该对象阻止释放其他对象。
  3. 所以,而不是:

    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);
    }
    

    这些提示帮助我减少了记忆和对象消耗。

    免责声明:我不能证明这是否是一种好的做法。

答案 2 :(得分: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)以释放对该对象的引用。您可以在Systematically Releasing Objects文章中详细了解相关内容。

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

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

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

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