eWilli
eWilli

Reputation: 107

Outlook 2010 VSTO AddIn: UI freezes randomly, while adding folders to PST asynchronously

My C# VSTO Outlook 2010 AddIn adds hundreds of MAPI folders to an imported pst file asynchronously. (pst exists in Outlook foldertree)

Here is an example:

Task.Factory.StartNew(() => {
    ... //get pstStore
    var rootFolder = pstStore.GetRootFolder();
    for (int i = 0; i < 500; i++)
    {
      var folder = rootFolder.Folders.Add("Test" + DateTime.Now.Ticks);
      Thread.SpinWait(1000); //emulate work
      Marshal.ReleaseComObject(folder);
    }
    Marshal.ReleaseComObject(rootFolder);
});

The Outlook UI freezes randomly at rootFolder.Folders.Add(...) for 2-3 sec. Sometimes after 20, sometimes after 50 added folders.

Any help / tip will be much appreciated.

Upvotes: 3

Views: 331

Answers (1)

Dmitry Streblechenko
Dmitry Streblechenko

Reputation: 66276

Expect your addin to stop working completely in Outlook 2013 or newer: OOM raises an error as soon as it detects that it is being called from a thread other than the primary Outlook thread. Note that this applies to the COM addins only as they run inside the outlook.exe address space. Out-of-proc access is always marshaled to the main Outlook thread by the COM system (but that defeats the whole purpose of using OOM from a separate thread).

Only Extended MAPI (C++ or Delphi) can be used on a secondary thread. If using Redemption is an option (it can be used from any language including C# - I am its author), its RDO family of objects can be used on secondary threads: store the value of the Namespace.MAPIOBJECT property on the primary thread (it is IMAPISession MAPI interface), then on the secondary thread create an instance of the RDOSession object (that will initialize MAPI on that thread) and set the RDOSession.MAPIOBJECT property to the value stored on the main thread - this way the two will share the same MAPI session.

Off the top of my head:

object mapiObject; //on the class/global level
..
mapiObject = Application.Session.MAPIOBJECT;
...
Task.Factory.StartNew(() => {
  Redemption.RDOSession session = new Redemption.RDOSession();
  session.MAPIOBJECT = mapiObject;
  Redemption.RDOStore pstStore = session.Stores["YourStoreName"];
  Redemption.RDOFolder rootFolder = pstStore.IPMRootFolder;
  Redemption.RDOFolders folders = rootFolder.Folders;
  for (int i = 0; i < 500; i++)
  {
     var folder = folders.Add("Test" + DateTime.Now.Ticks);
     Marshal.ReleaseComObject(folder);
  }
  Marshal.ReleaseComObject(folders);
  Marshal.ReleaseComObject(rootFolder);
  Marshal.ReleaseComObject(session);
}

Upvotes: 3

Related Questions