运行 循环遍历邮件项目时内存不足
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 对象引用计数的建立:
- 使用
System.Runtime.InteropServices.Marshal.ReleaseComObject()
释放 COM 对象。这会在对象上强制使用 COM "release"。
- 不要
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
有关详细信息和示例代码,请参阅以下文章:
- How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
- How To: Use Restrict method to retrieve Outlook mail items from a folder
此外,您可能会发现应用程序 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);
这应该也有效。
您好,我有一个 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 对象引用计数的建立:
- 使用
System.Runtime.InteropServices.Marshal.ReleaseComObject()
释放 COM 对象。这会在对象上强制使用 COM "release"。 - 不要
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
有关详细信息和示例代码,请参阅以下文章:
- How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
- How To: Use Restrict method to retrieve Outlook mail items from a folder
此外,您可能会发现应用程序 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);
这应该也有效。