Threads, MTA and COM objects

R

Reverend

What I thought would be a simple task has turned out to be touch (for me).
I have a COM ActiveX component from a 3d party vendor that I need to include
in one of our office applications. I have never worked with COM or ActiveX
objects in a C# program and my knowledge of the two technologies is very
limited.

The problem I'm experiencing is that my COM object is created on the
[STAThread] Main thread my application is started on. This makes the COM
object happy as it requires a STA thread. I then have events coming in from
various worker Threads from other parts of the application. These threads
are MTA and any attempts to work with the COM object result in a
InvalidActiveXStateException - this I understand is due to the fact we are
on a MTA thread.

The gist of the problem is that I need to marshal my ThreadPool invoked
events onto the original thread the COM object was created on. I have
experimented with using the SynchronizationContext class to store the
context that the COM object was created on, then marshal my events into that
context. The problem is System.Threading.SynchronizationContext.Current is
null in the Main() function.

As soon as I ran into trouble I backed off and started with a very simple
test application:

using System;
using System.Threading;

namespace ThreadContextTest
{
static class Program
{
private static FakeCOMObjectThatRequiresSTA _comObject;

[STAThread]
static void Main()
{
// Create the COM object
_comObject = new FakeCOMObjectThatRequiresSTA();

LogOperationContext("COM object created");
System.Threading.ThreadPool.QueueUserWorkItem(
new System.Threading.WaitCallback(OnDoSomething));
}

static void OnDoSomething(object state)
{
LogOperationContext("Event received");

try
{
// Try to work with the COM object
//
// This is where I need to get execution onto the thread
// that created the COM object.
_comObject.DoSomething();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}

static void LogOperationContext(string operation)
{
Console.WriteLine(String.Format(
"{0} on {1} thread {2}",
operation,
Thread.CurrentThread.GetApartmentState().ToString(),
Thread.CurrentThread.ManagedThreadId));
}
}

class FakeCOMObjectThatRequiresSTA
{
public FakeCOMObjectThatRequiresSTA()
{
CheckApartmentStateThrowIfNotSTA();
}

public void DoSomething()
{
CheckApartmentStateThrowIfNotSTA();
}

private void CheckApartmentStateThrowIfNotSTA()
{
if (Thread.CurrentThread.GetApartmentState()
!= ApartmentState.STA)
{
throw new Exception("Must be invoked in STA thread");
}
}
}
}


I would really appreciate any suggestion anyone might have. I will keep
reading and researching these new technologies, but a hint in the right
diretion would be awesome.
Thanks for reading!
 
R

Reverend

I have found two possible solutions to this problem:
1) Drop the ThreadPool and create my own Thread initialized as STA
2) Implement a custom SynchronizationContext object to handle the
marshalling.

If anyone has experience with either solution and can share any additional
information I would appreciate it.

Reverend said:
What I thought would be a simple task has turned out to be touch (for me).
I have a COM ActiveX component from a 3d party vendor that I need to
include in one of our office applications. I have never worked with COM
or ActiveX objects in a C# program and my knowledge of the two
technologies is very limited.

The problem I'm experiencing is that my COM object is created on the
[STAThread] Main thread my application is started on. This makes the COM
object happy as it requires a STA thread. I then have events coming in
from various worker Threads from other parts of the application. These
threads are MTA and any attempts to work with the COM object result in a
InvalidActiveXStateException - this I understand is due to the fact we are
on a MTA thread.

The gist of the problem is that I need to marshal my ThreadPool invoked
events onto the original thread the COM object was created on. I have
experimented with using the SynchronizationContext class to store the
context that the COM object was created on, then marshal my events into
that context. The problem is
System.Threading.SynchronizationContext.Current is null in the Main()
function.

As soon as I ran into trouble I backed off and started with a very simple
test application:

using System;
using System.Threading;

namespace ThreadContextTest
{
static class Program
{
private static FakeCOMObjectThatRequiresSTA _comObject;

[STAThread]
static void Main()
{
// Create the COM object
_comObject = new FakeCOMObjectThatRequiresSTA();

LogOperationContext("COM object created");
System.Threading.ThreadPool.QueueUserWorkItem(
new System.Threading.WaitCallback(OnDoSomething));
}

static void OnDoSomething(object state)
{
LogOperationContext("Event received");

try
{
// Try to work with the COM object
//
// This is where I need to get execution onto the thread
// that created the COM object.
_comObject.DoSomething();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}

static void LogOperationContext(string operation)
{
Console.WriteLine(String.Format(
"{0} on {1} thread {2}",
operation,
Thread.CurrentThread.GetApartmentState().ToString(),
Thread.CurrentThread.ManagedThreadId));
}
}

class FakeCOMObjectThatRequiresSTA
{
public FakeCOMObjectThatRequiresSTA()
{
CheckApartmentStateThrowIfNotSTA();
}

public void DoSomething()
{
CheckApartmentStateThrowIfNotSTA();
}

private void CheckApartmentStateThrowIfNotSTA()
{
if (Thread.CurrentThread.GetApartmentState()
!= ApartmentState.STA)
{
throw new Exception("Must be invoked in STA thread");
}
}
}
}


I would really appreciate any suggestion anyone might have. I will keep
reading and researching these new technologies, but a hint in the right
diretion would be awesome.
Thanks for reading!
 
S

Steve K

Peter said:
It's difficult to provide specific advice, without more details. Is
this a console application or a GUI? If GUI, is it Forms, WPF,
something else?
It is a WinForms application, although the user will rarely interact with the user interface, it is more of a
notification application, similar to how Outlook bubbles up a little dialog in the corner of your screen when new
messages arrive.
How long does this COM object live?
The COM object will live for the duration of the application, it is the "worker" in the application that interfaces with
external services.
Why were you using
the ThreadPool in the first place?
The ThreadPoll is part of another aspect of the application. Other applications will execute this application and pass
command lines arguments. There is code in place (not mine) that will manage a single instance of the application and
pass the command lines arguments from any subsequent instances to the original instance. It uses ThreadPool to create a
named pipe stream server to accept the command lines arguments. It's not my code, but it works very well and is
pretty simple.
I definitely wouldn't bother implementing a SynchronizationContext
sub-class. Just seems like too much effort for something that could
easily be handled via a simple synchronized queue of delegates (with the
main thread being the consumer of the queue).
The one example (which is very close to what I'm after) that I cam across did something very similar to this. Overall
it seems like way too much work for what I need to accomplish, but then again considering how little I know about COM
and threading (beyond the very basics) baybe this IS what it takes.
And in fact, that's one approach you might consider. But, if your main
thread is available to sit there and consume a queue of delegates, it
begs the question as to why are you executing your COM object's code on
a different thread, rather than just using it on that otherwise-idle
main thread.

Another approach, if your main thread is actually busy doing something
else, is to create a single delegate consumer thread that you've
initialized as STA. This would be just like the previous suggestion,
except that you've dedicated a new thread for the purpose. You could
still use the ThreadPool for the COM object itself if you want, but
you'd use the STA thread to execute code that requires STA by producing
delegates for the STA thread to consume.

Of course, if that means that _all_ of the COM code would wind up being
executed on that thread, then it does seem like a more useful, simple
approach would be to just dedicate that STA thread to the COM object
itself, rather than having it be a general-purpose "execute delegates on
an STA thread" queue consumer.

Which brings the question back to, if you're going to just dedicate a
thread to the COM object, why can't that happen on the main thread?
What was your intent in using the ThreadPool in the first place? Were
you actually using the ThreadPool in an appropriate way, and if so why
is it a viable solution to create a whole new thread dedicated to the
COM object?
I will need to read your response a couple more times to let it really soak in - there is a lot of information here and
a lot for me to think about. I appreciate you sharing all this, I'm sure my solution is in here somewhere.
I also can't say that I really understand your "very simple test
application". You don't have anything in your Main() method to wait for
the queued work item to complete, so the most likely outcome of that
program is for the process to exit before your work item gets to run at
all (though that's not guaranteed, since it's up to the Windows thread
scheduler as to what actually happens for sure).
The only purpose of the sample application was to illustrate the various ApartmentState values and where they would be
problems for me. I was trying to keep it very simple and probably assumed anyone looking at it would either step
through the debugger or review the Console output. It is a bit confusing though, I can see that.

Basically what I'm going to do is encapsulate the COM/ActiveX component as a Singleton that will live in the main
Program class. The various Form instances will then be able to access this object without any problems and I will also
be able to work with it when new command lines arguments are received.

The solution I used to ensure there is only a single instance of my application running can be found here:
http://www.flawlesscode.com/post/2008/02/Enforcing-single-instance-with-argument-passing.aspx

I don't want to lean on the Form and it's STA context to solve the COM issue. I have found a couple examples that use
this approach and I'm not sure that I will always have Forms in this application and as such I don't want to rely on them.

Thanks again for your detailed response, I will be reading it a couple times to understand all the various options here.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top