none
Is ApplicationClass Thread Safe? RRS feed

  • Question

  • I'm working on a multi-threaded service some other developer(s) wrote, and it uses a singleton instance of

    Microsoft.Office.Interop.Word.ApplicationClass to hand to multiple worker threads that generate some documents and save them to disk.

     

    Is this class/library thread-safe such that using a singleton instance for all threads is a safe thing to do? I can't find jack squat regarding this on the INET. The singleton Application is creating a new document on every thread, is this sufficient to be thread-safe?

     

    Thanks.

    Friday, May 30, 2008 6:47 PM

Answers

  • Hi,

     

    The Office object model is generally Single Threaded Apartment (STA).  This means that COM will serialize all incoming calls.  So to answer your question, the Word Application object is thread-safe because concurrency is simply prevented.  What this means is that multi-threaded solutions involving Office may not offer any benefit over single threaded solutions if they attempt to make object model calls from multiple threads since the lack of concurrency support will result in threads being blocked much of the time.  Typically, multi-threaded Office solutions are best implemented by making all object model calls from a single thread and then delegating the remaining (non-Office) work to background threads.

     

    Sincerely,

     

    Geoff Darst

    Microsoft VSTO Team

     

    Monday, June 2, 2008 4:19 PM
    Answerer

All replies

  • Not sure about your question. Anyway read this before you go crazy...

    http://support.microsoft.com/kb/257757

     

     

    Friday, May 30, 2008 9:51 PM
  •  romysree1 wrote:

    Not sure about your question. Anyway read this before you go crazy...

    http://support.microsoft.com/kb/257757

    Lots of good info there, thanks man.

    Friday, May 30, 2008 10:47 PM
  • Hi,

     

    The Office object model is generally Single Threaded Apartment (STA).  This means that COM will serialize all incoming calls.  So to answer your question, the Word Application object is thread-safe because concurrency is simply prevented.  What this means is that multi-threaded solutions involving Office may not offer any benefit over single threaded solutions if they attempt to make object model calls from multiple threads since the lack of concurrency support will result in threads being blocked much of the time.  Typically, multi-threaded Office solutions are best implemented by making all object model calls from a single thread and then delegating the remaining (non-Office) work to background threads.

     

    Sincerely,

     

    Geoff Darst

    Microsoft VSTO Team

     

    Monday, June 2, 2008 4:19 PM
    Answerer
  •  Geoff Darst - MSFT wrote:

    Hi,

     

    The Office object model is generally Single Threaded Apartment (STA).  This means that COM will serialize all incoming calls.  So to answer your question, the Word Application object is thread-safe because concurrency is simply prevented.  What this means is that multi-threaded solutions involving Office may not offer any benefit over single threaded solutions if they attempt to make object model calls from multiple threads since the lack of concurrency support will result in threads being blocked much of the time.  Typically, multi-threaded Office solutions are best implemented by making all object model calls from a single thread and then delegating the remaining (non-Office) work to background threads.

     

    Sincerely,

     

    Geoff Darst

    Microsoft VSTO Team

     

    Thanks for that answer Geoff.

     

    Follow-up question: If I have every one of my threads instantiate their own ApplicationClass will COM still serialize everything? I'm willing to incur the additional overhead if it will let me run threads autonomously.

    Monday, June 2, 2008 6:16 PM
  • Hi,

     

    Yes, COM will serialize everything so you won't have any threading issues in the scenario you describe.  Just to make sure my previous response was clear; if all your code does is create and save documents, you would be much better off to perform those actions iteratively in a loop rather than trying to use multiple threads.  The serialization will wreak havoc with thread scheduling and slow things down--probably to the point where trying to use multiple threads will be slower than an iterative solution. 

     

    The other thing I forgot to mention is that you need to implement a COM IMessageFilter if you are going to use multiple threads.  The issue is that Word may not be in a state to handle your call when it comes in and can reject it. If you don't implement and register an IMessageFilter, can potentially get an RPC_E_CALLREJECTED exception thrown from any object model call that you make from the non-UI thread.  If you are only making a few calls, you could just handle the exception and use a looping construct to retry the call.  However, the preferred approach is to register an IMessageFilter implementation. IMessageFilter::RetryRejectedCall allows you to create a single handler for this situation.  Typically, your implementation would automatically retry the call for some amount of time, after which it would call OleUiBusy to display the OLE busy dialog.

     

    You may well have been fairly lucky and not have seen the RPC_E_CALLRJECTED error to this point since the timing would have to be right to get it.  However, you can reproduce it if you can display a modal dialog (such as File Open) while your solution is attempting to execute.

     

    If you implement an IMessageFilter (or handle the exception in *all* cases of object model calls from non-UI threads) your solution will be correct.  However, IMO the overhead of doing this combined with the fact that there is no benefit from using multiple threads strongly favors avoiding this approach.

     

    Sincerely,

     

    Geoff Darst

    Microsoft VSTO Team

     

    Tuesday, June 3, 2008 2:43 PM
    Answerer
  • [Man I love it when I type up a reply and hit Post and the forums tell me I've been successfully logged out. /argh!]

     

    I went and did some reading on the subject because this is all rather new to me and I want to make sure I understand what's going on. Things are still a bit muddy to me, but I think I have the gist of it.

    • A .NET process has to create thread apartments in order to interact with COM.
    • Any COM objects marked STA will live in the STA apartment inside the .NET process, no matter what (.NET) thread created them.
    • Any (.NET) threads that try to interact with any COM objects living in the STA apartment will be marshaled to a single thread by the interop, essentially rendering multi-threading ineffective.

    Do I have that right? The third point above is true even when any given (STA) COM object is instantiated and accessed only by a single (.NET) thread, correct?

    Tuesday, June 3, 2008 5:00 PM
  • Hi,

     

    Yes, you have #3 right.  The Ole RPC protocol for marshalling to an STA involves packaging the call up and actually sending a message to a hidden window (which Ole supplies) in the server process.  When the server dispatches that message, the Ole window's WndProc unwraps the message and executes the call.  The message protocol enforces serialization.  Incoming calls are queued up in the message queue and they are handled in sequence by the GUI thread.

     

    Sincerely,

     

    Geoff Darst

    Microsoft VSTO Team

     

    Tuesday, June 3, 2008 10:34 PM
    Answerer
  • Cool, thanks for your time Geoff.

     

    Tuesday, June 3, 2008 10:48 PM
  • Hi Geoff

     

    Read your earlier answers and got answers to *some* of my questions. I am working on an Outlook Addin(C++ + MFC) and my addin does a lot of non-Office work. My addin throws a UI with a progress and does some non-office work(say extracting all the links from the HTML dumps of emails) with the selected emails. Now the issue is that the UI hangs-up while I do my work. So I thought to give multi-threading a try. What I did was, I created a UI in the thread 0(the thread which enters my addin through OnConnection) and created a seperate thread(the thread was created by implemnting CWinThread). It did prove to be a successful attempt but then came accross this article: http://www.outlookcode.com/article.aspx?id=71 which seems to be due to multiple threads accessing the Object Model at the same time. Do you think that RPC_E_CALLRJECTED might have been the issue. Reading this article, I thought that outlook addin, actually, should not spawn multiple threads. Can my Outlook addin be multi threaded? Can there be performance gain from it, if I am doing a lot of *non-office* work? Is there some other way of making my UI responsive?

     

    Aditya
    Saturday, June 7, 2008 7:37 AM
  • Hi Aditya,

     

    Your Outlook (or any Office) add-in can be multi-threaded.  If you plan on making calls to the Office object model from a background thread, you must either implement and register (via CoRegisterMessageFilter) the Com IMessageFilter interface or you must handle the RPC_E_CALLREJECTED exception at every call.  Without seeing the callstack, there is no way for me to know whether failure to do this is the cause of the ITunes issue, but it would be a good bet. 

     

    The other thing you have to be aware of is that interface pointers to STA bound objects must be marshalled across apartment boundaries.  IOW, you can't just pass an interface pointer between threads (or reference a pointer that is a class member).  You must use CoMarshalInterThreadInterfaceInStream (or you can use CoMarshalInterface along with the Stream APIs to do the same thing).

     

    I don't believe the article is correct in that the problem isn't fixable in VB6 (other than changing code to make OM calls from the UI thread).  If you do the above, it will fix the problem--though doing the above in VB6 may be non-trivial.

     

    Although your add-in *can* be multi-threaded, because COM serializes calls to the Office OM, it is not possible for concurrent calls to be made on different threads.  If multiple threads try to access the OM concurrently, those threads will all be blocked (which will have a negative implact on performance).  Because of this, it is generally better (in the performance sense) to architect your solution so that background threads do not call into the Office object model.  For example, you could do your OM processing in a loop and dump all of the information your background worker threads will need into a queue that your worker threads pull from.  To keep your UI responsive while your loop is executing, you just need to pump messages on each pass through your loop.  One way to do that is to call Application.DoEvents.  I'm a little leery of that approach since a VSTO solution is not a Windows Forms application, but it will work.  The alternative is to implement your own message pump, which would involve P/Invoking PeekMessage, TranslateMessage and DispatchMessage.  Typically, you would do something like this:

     

    while (PeekMessage(...))

    {

        TranslateMessage(...)

        DispatchMessage(...)

    }

     

    The above loop (placed at the bottom of your "work" loop) would dispatch all pending messages in the queue.  Depending on what you were trying to accomplish, PeekMessage will let you limit the messages you pull out of the queue to a certain range.

     

    Again, you *can* make object model calls from different threads provided you follow the guidance above.  If your threads spend most of their time doing non-object model related work, you may find that the performance is satisfactory--though keep in mind that thread timing can vary between machines. 

     

     

    Sincerely,

     

    Geoff Darst

    Microsoft VSTO Team

     

    Monday, June 9, 2008 2:25 PM
    Answerer
  •  

    Thanks Geoff for your reply.

     

    The problem of *UI hanging* is actually not the UI which I throw up but the Outlook key processing. During the time I do my "work", the outlook does not process keystrokes(Ctrl-N etc.) and preview pane is not refreshed. I tried all the experiments by putting the message loop at the end of wach wrk cycle.

     

    for all mails

    do some wrk

    MessageLoop

    end

     

    From this, the dialog I throw remains responsive, but in case I press Ctrl-N in between, the kesy does not get processed. The preview pane also does not refresh during this.

     

    Any hints in this regard?

     

    Aditya

    Monday, June 9, 2008 2:52 PM
  • Hi Aditya,

     

    Ctrl+N is an accelerator key, so you would need to call TranslateAccelerator instead of TranslateMessage from within your message pump in order for it to be processed correctly.  The problem is that there isn't really an good way to get Outlooks accelerator table handle. 

     

    I'm not sure why the preview pane wouldn't be refreshing.  Provided you are dispatching WM_PAINT, etc. I would expect it to update. 

     

    If you expect Outlook to be fully responsive while you do your work, you must somehow allow Outlook to pump messages using its main message loop.  There are only a couple of options here.  The first is to move your UI and your Outlook calls to a background thread.  Since your UI is just a progress bar, this should be reasonably safe.  You must implement IMessageFilter and be prepared to handle rejected calls.  If your solution makes all of its object model calls from this backround thread, you won't have to worry about contention between your own threads, but you still may end up being blocked because Outlook is in the middle of doing something resulting from a user UI gesture.  Additionally, if the user causes a modal dialog to display (and leaves it open), Outlook will never handle your calls, so you need to implement your IMessageFilter::RetryRejected call such that after a certain period of time it will display the OLEBusy dialog (by calling OleUIBusy).

     

    A second approach would be to perform your work in small pieces during idle time.  To do this, you would either need to subclass Outlook's main window and handle WM_IDLE by doing a "unit" of work (something fast so the system stays responsive).  The alternative would be to implement IOleComponent and leverage IOleComponentManager.  I believe that Outlook implements this (call CoRegisterMessageFilter to get Outlook's message filter and then QueryInterface it for IOleComponentManager).  If that works, you can register yourself as a component and get idle time that way.

     

    The approach to take really just depends on how deterministic you need to be about completing your work.  The threading approach will guarantee that cpu cycles are allocated for doing work (though it can't guarantee that Outlook will be available to handle the calls).  The idle-time processing approach will guarantee that your Outlook calls will always execute, but there are now guarantees about when idle time will actually occur.

     

    Sincerely,

     

    Geoff Darst

    Microsoft VSTO Team

     

    Monday, June 9, 2008 3:57 PM
    Answerer
  •  

    Thanks Geoff for your precious time and help!

     

    I shall start doing all the changes you have suggested soon and would ask you again if required

     

    Aditya

    Monday, June 9, 2008 4:05 PM