Excel interop COM exception while running in background

14,509

You have to be sure that you get the root Excel Application object, and from there the Range, from the thread where the work is being done. The Excel COM objects all live in a Single-Threaded Apartment (STA) so you can't just use them from some other thread.

You don't show how "cellUtility.GetCell" actually gets the Range, but the likely problem is that you are using an Application object that was initially retrieved from another thread.

Doing this kind of work on a "background thread, so your GUI can stay responsive" is problematic - all work in Excel eventually happens on the main Excel thread, since Excel is (essentially) single-threaded.

There are some ways to approach this:

  1. Often you'll find that switching off ScreenUpdating and setting Calculation to Manual allows you to do the editing work much faster, then you don't need other threads or anything.

  2. Run your work on the main thread, but break it into small chunks, then schedule the next chunk to be done after yielding to Excel - you can create a Windows.Forms.Timer or if you can run an Excel macro use Application.OnTime to schedule the next piece of work.

  3. If you want to do the work from another thread, you need to get the Application object and further COM object on that thread. It can be tricky to get the right Excel Application instance, but Andrew Whitechapel describes a good approac here: http://blogs.officezealot.com/whitechapel/archive/2005/04/10/4514.aspx. However, you will still have to check for errors on every COM call from another thread, since Excel might be busy and might reject any COM call from another thread at any time (particularly if your users are interacting with that 'responsive GUI'. You need to at least check for COMExceptions with the errors - every COM call can throw one of these:

    • const uint RPC_E_SERVERCALL_RETRYLATER = 0x8001010A;
    • const uint VBA_E_IGNORE = 0x800AC472;

Excel-DNA (the Excel / .NET integration library I develop) gives access to an Application object on the thread you are running by calling ExcelDnaUtil.Application. But I still recommend moving all interaction with the Excel object model to the main Excel thread, perhaps using a call to Application.Run to run tell Excel to run a macro. The Application.Run call then becomes a single point where the COMException can be checked and retried from your background thread.

Share:
14,509
Vajda
Author by

Vajda

Updated on June 11, 2022

Comments

  • Vajda
    Vajda almost 2 years

    I'm trying to do apply some styles to cells in my workbook. And I want to do it in background thread so my GUI can stay responsive. This job should take few seconds, and if I click on some random cell in my document I'll get an exception. Here is my code:

    public void ApplyStyles()
    {
        BackgroundWorker bw = new BackgroundWorker();
        bw.DoWork += DoWork;
        bw.RunWorkerAsync();
    
    }
    
    private void DoWork(object sender, DoWorkEventArgs e)
    {
        try
        {
            foreach (ICell xcell in cells)
            {
                Microsoft.Office.Interop.Excel.Range cell = cellUtility.GetCell(xcell);
                if (styles.ContainsKey(styleIds[xcell.Style]))
                {
                    Style s = styles[xcell.Style];
                    cell.Style = s;
                }
            }
        }
        catch (Exception ex)
        {
            if (Logger.IsErrorEnabled)
            {
                Logger.Error(ex.ToString());
            }
            messageBox.ShowErrorMessage(localizationMessages.ApplyingErrorText, localizationMessages.ApplyingErrorCaption);
        }
    }
    

    When exception happens, this is the message I'm receiving;

    System.Runtime.InteropServices.COMException (0x800AC472): Exception from HRESULT: 0x800AC472
       at System.RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Int32[] aWrapperTypes, MessageData& msgData)
       at Microsoft.Office.Interop.Excel.Range.set_Style(Object value)
       at ABZ.ReportFactory.OfficeAddin.Excel.BatchLinking.BackgroundStyleApplier.DoWork() in C:\ABZ\ABZ ReportFactory Office Addin\ABZ.ReportFactory.OfficeAddin.Excel\BatchLinking\BackgroundStyleApplier.cs:line 86
    

    Is it possible to do this style applying operation in background thread? And how should I do it?

    Second problem I have is that while this style applying is running in background my cursor is constantly changing state from busy to regular until this operation is over. I would like for cursor to be normal. That user is totally unaware of this background operation.

    cheers, Vladimir

  • Vajda
    Vajda about 12 years
    It seems impossible for me to create application instance from another thread since I use DI for that and I only have one instance per process. I'll maybe just ignore that exception and try to repeat operation until it's over.
  • Govert
    Govert about 12 years
    Couldn't you 'dependency inject' a helper object with a method to retrieve the Application object on the thread it's running? If you really are trying to talk to the Excel object model through a STA COM object on the wrong thread, you're in an unsupportable configuration. Even if you get the Application on the right thread, you'd still need to check and handle those errors.