如何使用 VSTO 加快获取 Outlook 文件夹中所有联系人的 EntryID 值
How to speed up getting EntryID values for all contacts in Outlook folder using VSTO
我需要获取 Outlook 文件夹中所有联系人的 EntryID。如果我在一个文件夹中有 6000 个联系人,则执行大约需要 100 秒(无论是后台线程还是主线程都没有关系,我都试过了)。代码是这样的:
List<Outlook.ContactItem> contactItemsList = null;
Outlook.Items folderItems = null;
Outlook.MAPIFolder folderSuggestedContacts = null;
Outlook.NameSpace ns = null;
Outlook.MAPIFolder folderContacts = null;
object itemObj = null;
try
{
contactItemsList = new List<Outlook.ContactItem>();
ns = Application.GetNamespace("MAPI");
// getting items from the Contacts folder in Outlook
folderContacts = ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderContacts);
folderItems = folderContacts.Items;
for (int i = 1; folderItems.Count >= i; i++)
{
itemObj = folderItems[i];
if (itemObj is Outlook.ContactItem)
contactItemsList.Add(itemObj as Outlook.ContactItem);
else
Marshal.ReleaseComObject(itemObj);
}
Marshal.ReleaseComObject(folderItems);
folderItems = null;
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
}
finally
{
if (folderItems != null)
Marshal.ReleaseComObject(folderItems);
if (folderContacts != null)
Marshal.ReleaseComObject(folderContacts);
if (folderSuggestedContacts != null)
Marshal.ReleaseComObject(folderSuggestedContacts);
if (ns != null)
Marshal.ReleaseComObject(ns);
}
var ids = contactItemsList.Select(c => c.EntryID).ToArray();
收集物品的部分大约需要 5-8 秒,而最后一行大约需要 80-90 秒。
有没有更快的方法?我最初想到了 Items.SetColumns,但事实证明它不适用于 EntryID(这似乎是 Outlook 开发人员的奇怪决定,因为 EntryID 只是一个字符串,而 SetColumns 用于快速检索字符串属性)。
此外,即使使用 BackgroundWorker,Outlook 也几乎一直冻结(约 100 秒)。你可能不小心点击了一些东西,但它的 FPS 速率是 1-2 FPS 左右。似乎后台执行没有多大帮助。我怀疑这是因为当正在执行的任务不是 CPU 密集型但获取 EntryIDs 是繁重的操作并因此严重干扰 UI.
时后台执行很好
我还有一些遗留的 C++/COM 代码,它们的功能相同,但速度也很慢。看起来 .NET 互操作不是问题的根本原因。也许我应该使用另一个 API 调用?
我目前正在使用 Outlook 2010 64 位进行测试。
好的,知道了。将 "for" 分成块并保持同时存在的 Outlook.ContactItem 个对象的数量(就像我上面的评论)是优化的一部分,但更重要的是另一件事。
文档说您不能使用 Items.SetColumns("EntryID")
但从未解释原因。实际上,您不能将它传递到那里,因为总是返回 EntryID!因此,传递任何其他轻量级 属性(我使用 "Initials")就可以了。它 returns 仅设置了 EntryID 和 Initials 字段的对象,这将性能提高了 4-5 倍。
结合块优化,我现在在后台线程中可以在 6 秒内完成所有操作(主线程更快)。
使用 MAPITable.GetTable 在一次调用中从多个项目中检索属性而根本不打开它们(这非常昂贵)。
其次,OOM 不能在辅助线程上使用 - 它从未受到支持,并且 Outlook 2016 会在检测到除主 UI 线程之外的任何线程正在使用它时立即引发异常。只有扩展 MAPI(C++ 或 Delphi)是线程安全的。您还可以使用 Redemption (any language - I am its author) and its RDO 系列对象 - 它 100% 基于扩展 MAPI,可以从辅助线程使用。特别是,您可以使用
RDOFolder.Items.MAPITable.ExecSQL("SELECT EntryID FROM FOLDER WHERE MessageClass = 'IPM.Contact' ")
检索所有联系人的条目 ID 作为记录集。
我需要获取 Outlook 文件夹中所有联系人的 EntryID。如果我在一个文件夹中有 6000 个联系人,则执行大约需要 100 秒(无论是后台线程还是主线程都没有关系,我都试过了)。代码是这样的:
List<Outlook.ContactItem> contactItemsList = null;
Outlook.Items folderItems = null;
Outlook.MAPIFolder folderSuggestedContacts = null;
Outlook.NameSpace ns = null;
Outlook.MAPIFolder folderContacts = null;
object itemObj = null;
try
{
contactItemsList = new List<Outlook.ContactItem>();
ns = Application.GetNamespace("MAPI");
// getting items from the Contacts folder in Outlook
folderContacts = ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderContacts);
folderItems = folderContacts.Items;
for (int i = 1; folderItems.Count >= i; i++)
{
itemObj = folderItems[i];
if (itemObj is Outlook.ContactItem)
contactItemsList.Add(itemObj as Outlook.ContactItem);
else
Marshal.ReleaseComObject(itemObj);
}
Marshal.ReleaseComObject(folderItems);
folderItems = null;
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
}
finally
{
if (folderItems != null)
Marshal.ReleaseComObject(folderItems);
if (folderContacts != null)
Marshal.ReleaseComObject(folderContacts);
if (folderSuggestedContacts != null)
Marshal.ReleaseComObject(folderSuggestedContacts);
if (ns != null)
Marshal.ReleaseComObject(ns);
}
var ids = contactItemsList.Select(c => c.EntryID).ToArray();
收集物品的部分大约需要 5-8 秒,而最后一行大约需要 80-90 秒。
有没有更快的方法?我最初想到了 Items.SetColumns,但事实证明它不适用于 EntryID(这似乎是 Outlook 开发人员的奇怪决定,因为 EntryID 只是一个字符串,而 SetColumns 用于快速检索字符串属性)。
此外,即使使用 BackgroundWorker,Outlook 也几乎一直冻结(约 100 秒)。你可能不小心点击了一些东西,但它的 FPS 速率是 1-2 FPS 左右。似乎后台执行没有多大帮助。我怀疑这是因为当正在执行的任务不是 CPU 密集型但获取 EntryIDs 是繁重的操作并因此严重干扰 UI.
时后台执行很好我还有一些遗留的 C++/COM 代码,它们的功能相同,但速度也很慢。看起来 .NET 互操作不是问题的根本原因。也许我应该使用另一个 API 调用?
我目前正在使用 Outlook 2010 64 位进行测试。
好的,知道了。将 "for" 分成块并保持同时存在的 Outlook.ContactItem 个对象的数量(就像我上面的评论)是优化的一部分,但更重要的是另一件事。
文档说您不能使用 Items.SetColumns("EntryID")
但从未解释原因。实际上,您不能将它传递到那里,因为总是返回 EntryID!因此,传递任何其他轻量级 属性(我使用 "Initials")就可以了。它 returns 仅设置了 EntryID 和 Initials 字段的对象,这将性能提高了 4-5 倍。
结合块优化,我现在在后台线程中可以在 6 秒内完成所有操作(主线程更快)。
使用 MAPITable.GetTable 在一次调用中从多个项目中检索属性而根本不打开它们(这非常昂贵)。
其次,OOM 不能在辅助线程上使用 - 它从未受到支持,并且 Outlook 2016 会在检测到除主 UI 线程之外的任何线程正在使用它时立即引发异常。只有扩展 MAPI(C++ 或 Delphi)是线程安全的。您还可以使用 Redemption (any language - I am its author) and its RDO 系列对象 - 它 100% 基于扩展 MAPI,可以从辅助线程使用。特别是,您可以使用
RDOFolder.Items.MAPITable.ExecSQL("SELECT EntryID FROM FOLDER WHERE MessageClass = 'IPM.Contact' ")
检索所有联系人的条目 ID 作为记录集。