I'm getting very confused about whether C# marshal's COM objects between threads. To this end I have an application which is loading a set of files in a task parallel fashion. I'm using the StaTaskScehduler to load the files using the COM object. Once the COM object is loaded I am storing the object in a central list.
I then later try to perform some processing on this data, again using the STATaskScheduler. However at this point I hit a problem. I receive an exception as follows:
An unhandled exception of type 'System.Runtime.InteropServices.InvalidComObjectException' occurred in MadCat.exe
Additional information: COM object that has been separated from its underlying RCW cannot be used
Now my understanding is that I receive this error because the object hasn't been marshalled into the new thread. I thought this was something C# does for you?
How can I create an apartment threaded COM object in one thread and then use it from another thread?
Am I barking up the wrong tree here? Should I not even be using the Sta apartment for my threads? I can guarantee that the object is never access from multiple threads simultaneously. Any thoughts much appreciated.
Edit: The COM object is defined as follows:
[
coclass,
threading( apartment ),
vi_progid( [Namespace.Class] ),
progid( [Namespace.Class].6 ),
version( 6.0 ),
uuid( GUID_C[Class] ),
helpstring( [Class]" Class" )
]
So by my understanding this is an apartment threaded object, right? I've just tried using a modified task scheduler that doesn't set the apartment state (MTA by default?). This object then does seem to work when I create it in one thread and use it from another. Is this safe or will it come back to bite me some other way?
COM's threading model has always confused the hell out of me :/
It appears you're using Stephen Toub's StaTaskScheduler
as a part of some "stateful" logic, where your COM objects live across StartNew
boundaries. If that's the case, make sure you create and use these objects on the same StaTaskScheduler
STA thread and nowhere outside it. Then you wouldn't have to worry about COM marshaling at all. Needless to say, you should create StaTaskScheduler
with only one thread, i.e., numberOfThreads:1
.
Here's what I mean:
var sta = new StaTaskScheduler(numberOfThreads:1);
var comObjects = new { Obj = (ComObject)null };
Task.Factory.StartNew(() =>
{
// create COM object
comObjects.Obj = (ComObject)Activator.CreateInstance(
Type.GetTypeFromProgID("Client.ProgID"));
}, CancellationToken.None, TaskCreationOptions.None, sta);
//...
for(int i=0; i<10; i++)
{
var result = await Task.Factory.StartNew(() =>
{
// use COM object
return comObjects.Obj.Method();
}, CancellationToken.None, TaskCreationOptions.None, sta);
}
If Obj.Method()
returns another COM objects, you should keep the result in the same StaTaskScheduler's "apartment" and access it from there, too:
var comObjects = new { Obj = (ComObject)null, Obj2 = (AnotherComObject)null };
//...
await Task.Factory.StartNew(() =>
{
// use COM object
comObjects.Obj2 = comObjects.Obj.Method();
}, CancellationToken.None, TaskCreationOptions.None, sta);
If you also need to handle events sourced by Obj
, check this:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With