简体   繁体   中英

How to keep Outlook UI responsive in C# VSTO addin when making lots of CPU-intensive work on UI thread

I have the code which makes many calls to Outlook API in a loop. Outlook doesn't like access to its API from worker threads so I have to use main thread for that. The part of the code which makes network queries is perfectly fine with async/await so the UI does not freeze. But when the code reaches CPU-intensive part (calls to Outlook API), UI obviously freezes.

I inserted await Task.Delay(1) which executes after each 10ms elapses but since timer resolution is not so precise (15ms), it makes the code noticeably slower (although unblocks UI).

DateTime d1 = DateTime.Now;
int interval = 10;
foreach (SomeItem item in items)
{
    // Intense access to Outlook API...
    // ...

    DateTime d2 = DateTime.Now;
    if (d2.Subtract(d1).TotalMilliseconds > interval)
    {
        await Task.Delay(1);
        d1 = d2;
    }
}

Using Task.Yield instead of Task.Delay(1) does not unblock UI (MSDN anyway doesn't recommend Task.Yield for this).

Increasing interval from 10ms to larger values (100ms) reduces performance loss to the minimum and makes UI somewhat responsive but not so smooth.

Maybe there is a better alternative which wouldn't imply any tradeoffs between UI smoothness and performance? Maybe like a good old Application.DoEvents but in a modern and recommended way?

There is no good way to do that using OOM alone. Extended MAPI is thread safe, but it is only accessible from C++ or Delphi. You can use Redemption (any language) - its RDO family of objects is purely MAPI based and can be used from a secondary thread. Save Application.Session.MAPIOBJECT into a dedicated variable on the main thread (this way only that variable would have to be marshalled). On the secondary thread, create an instance of the RDOSession object (this way the MAPI system will be initialized on that thread) and set its MAPIOBJECT property to the variabel saved on the main thread. This way the two will share the same MAPI session. You can then retrieve the folder using RDOSession.GetFolderFromID / GetDefaultFolder /etc. and process the items.

As you probably know OOM can't be used from secondary threads, or you may get an exception in the code. The latest versions of Outlook
detect such use cases and may throw an exception. But you are free to use a low-level API on which Outlook is based on - Extended MAPI or any other wrapper around that API such as Redemption.

Note, in case if you deal only with Exchange profiles you may consider using EWS or Outlook API for getting the required data on secondary threads. See EWS Managed API, EWS, and web services in Exchange for more information.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM