Search code examples
multithreadingexcelvb.net-2010excel-dna

Disposing of ExcelDnaUtil.Application from new thread


I'm using ExcelDNA to develop an XLL.

In it, I have a form living in a DLL to which I pass "ExcelDnaUtil.Application" as a member to facilitate interactions between the form and the instance of Excel running the XLL.

If I launch the form in the main thread using:

form1.show()

when I close the form then close Excel, Process Explorer shows that the Excel process is properly disposed of.

If I launch the form using a new thread:

Dim workerThread As Thread
workerThread = New Thread(Sub() form1.showdialog())
workerThread.Start()

when I close the form and then close Excel, the process remains in Process Explorer. I have been careful not to use two decimal points in any line of code and set the interface member to "nothing" when closing the form. I am not using "ReleaseCOMObject" as other articles have indicated that it is bad-practice.

Question: How do I dispose of the Excel process properly from a separate thread?


Solution

  • It's impossible to get the COM stuff right cross-thread.

    You should never talk to the Excel COM object model from another thread. If you follow this one simple rule, you never have to worry about two dots, never have to set anything to Nothing and never have to call any of the ReleaseComObject hacks. And Excel will close nicely.

    Since Excel is single-threaded (actually because the Excel COM object model lives in a Single-Threaded Apartment), there is no performance benefit from talking to Excel with another thread - internally it all gets marshalled to the main thread anyway.

    If you dare to talk to Excel from another thread, then any COM call could fail, at any time. This is because Excel is still 'alive' and can go into a state where the 'object model is suspended', causing all COM calls to fail (with errors that even a COM message filter can't catch). When would Excel go into such a stubborn mode? When the user does something crazy like click their mouse button, for example.

    How then to call back to Excel after you've done some work on another thread? Excel-DNA has a helper that will schedule work to be done back on the main thread, when Excel is in a mode where COM calls are safe. You just call ExcelAsyncUtil.QueueAsMacro(...) with a delegate containing the work to be done. This call can be made at any time from any thread, but the code will only run when ready.

    A somewhat clumsy example is this:

    Public Module MyFunctions
    
        Dim workerThread As Thread
    
        Public Function OverwriteMe() As Object
            Dim caller = ExcelDnaUtil.Application.Caller
            workerThread = New Thread( _
                Sub()
                    Thread.Sleep(5000)
                    ExcelAsyncUtil.QueueAsMacro( _
                        Sub()
                            caller.Value = "Done!!!"
                        End Sub)
                End Sub)
            workerThread.Start()
            Return "Doing it..."
        End Function
    End Module