So, following this, I decided to explicitly instantiate a COM object on a dedicated STA thread. Experiments showed that the COM object needed a message pump, which I created by calling Application.Run()
:
private MyComObj _myComObj;
// Called from Main():
Thread myStaThread = new Thread(() =>
{
_myComObj = new MyComObj();
_myComObj.SomethingHappenedEvent += OnSomthingHappened;
Application.Run();
});
myStaThread.SetApartmentState(ApartmentState.STA);
myStaThread.Start();
How do I post messages the the STA thread's message pump from other threads?
Note: I heavily edited the question for the sake of brevity. Some parts of @Servy's answer now seems unrelated, but they were for the original question.
No. The point of a message queue is that it needs to consume the thread's execution. A message queue is, in implementation, going to look very similar to your:
That is a message loop. What you're trying to do is run two different message loops in the same thread. You can't really do that (and have both queues pumping; one queue will, by necessity, pause execution of the other while it is running), it just doesn't make sense.
What you need to do, instead of creating a second message loop on the same thread, is send messages to your existing queue. One way of doing that is through the use of a
SynchronizationContext
. One problem however is that there aren't any events that can be hooked into to execute a method in the message pump with that overload ofRun
. We'll need to show aForm
just so that we can hook into theShown
event (at which point we can hide it). We can then grab theSynchronizationContext
and store it somewhere, allowing us to use it to post messages to the message pump:Keep in mind that the message queue that Windows creates for an STA thread is already an implementation of a thread-safe queue. So just use it for your own purposes. Here's a base class that you can use, derive your own to include your COM object. Override the Initialize() method, it will be called as soon as the thread is ready to start executing code. Don't forget to call base.Initialize() in your override.
It you want to run code on that thread then use the BeginInvoke or Invoke methods, just like you would for the Control.Begin/Invoke or Dispatcher.Begin/Invoke methods. Call its Dispose() method to shut down the thread, it is optional. Beware that this is only safe to do when you are 100% sure that all COM objects are finalized. Since you don't usually have that guarantee, it is better that you don't.