I am writing (and teaching myself how to write) an experimental assembly in VB.NET 3.5 that is exposed via COM and then called from some Excel VBA code to initiate an instance of a class that traps some Excel events and then perform some functions on the active workbook in Excel.
To initiate the class, a reference to the Excel Application is passed from the VBA (I am using a PIA for Excel in the assembly).
I needed to perform a time-consuming operation on the active workbook from my assembly so I decided to use a BackgroundWorker
in a WinForm so that I can display a progress dialog for the operation whilst the operation completes in the background.
I would like to know if there are any problems with interacting with Excel via COM using a background worker in this way? Reason for asking is that the main class in the assembly holds the reference to the Excel application object, and this is then passed to the BackgroundWorker
so that it can determine the active workbook and then perform some operations on it. I am only accessing the workbook itself (not other objects) through one procedure.
For some reason I have it in my head that Excel might not like this - am I right?
If you are refering to this:
Then, no; you cannot do that.
Excel VBA is an STA environment. All access to the Excel Object Model, in and out, must happen from the same thread. You don't get to pick the thread either; it has to be the thread that called your method to begin with.
To run code accessing Excel from a worker thread, you would have to "marshal" the interface pointer to your worker thread. Mashalling creates a pair of COM wrappers (called 'proxy/stub' pairs) that shuttle the method call accross threads as needed - this is similar to what happens when you use the
Form.Invoke()
method in .NET. I don't know off the top of my head how to do that in .NET. This would not be my first approach anyway.Another alternative is NOT to access the Excel object from your worker thread, but to use helpers in the main form object (you do want a form, don't you?). Let your worker thread call those helpers via
Form.Invoke()
to make sure they run from the UI thread (the thread that VBA used to call you). This is functionally the same as the first alternative, except that .NET does the work, instead of COM.A third approach, is to not use worker threads at all - do it the VB way: create your UI from the one and only thread; show it, and then do your work from the
Load
event of the Form. CallApplication.DoEvents()
once in a while (a few times a second) to let the UI operate with some reseamblance of smoothness.A fourth alternative is to turn the problem around. Use a brand new thread instead of a pool thread (which is what you get from
BackgroundWorker
) and handle the UI from there. We reverse roles here: the main thread would do the fancy work, while the extra thread creates a form and shows the UI. Again, make sure you useForm.Invoke()
to update the progress window. Make sure youDispose()
your thread when you're done. This might just be my preferred approach.Or just use an hourglass mouse pointer... Just how "slow" is your "slow"?
Don't know much about using BackgroundWorker. I have worked on a Winforms app that interfaced to Excel via PIA and COM. It used a plain old ThreadStart delegate to spin up a thread that showed a progress bar while files loaded in the background. Worked fine. As I understand it, BackgroundWorker works pretty much the same under the covers, so I would say go for it.