2

I have a c# static function that calls into a COM component.

When calling this function from a trivial WPF application it returns correctly. The code might look something like this:

var result = MyClass.StaticCall();
Debug.WriteLine("Hurrah!");

When I call that code, the variable will be set and the debug message output as expected.

If I wrap the same call in a thread, however, it never returns. The failing code might look something like this:

var foo = new Thread(new ThreadStart(() =>
                               {
                                   var result = MyClass.StaticCall();
                                   Debug.WriteLine("Hurrah!");
                               }));
foo.Start();

while (foo.IsAlive)
{
}

In this case, StaticCall will not return and the thread is blocked indefinitely.

What might be causing this behaviour?

Additional info:

  • Setting the appartment state of the thread makes no difference.
  • The last message in the visual studio output window is a notification that the COM interop has been loaded.
  • All calls into COM are isolated on the one thread.
Montgomery 'monty' Jones
  • 1,061
  • 2
  • 14
  • 27

3 Answers3

3

I seem to recall something about COM requiring an active message loop running in the thread on which COM interop is used. I don't know the details of .NET's COM interop implementation, but in good old Win32 if you tried to make an interprocess call on COM after doing all the special initialization steps it would still freeze at the COM call, just as you describe. Add a simple peekmessage loop to the background thread and the call would go through. Perhaps something similar needs to be done in your .NET code?

Background: COM interprocess comms rely on SendMessage via a window handle created by COM. If that window handle is created on your main thread, then messages sent to that window handle will be processed by your main thread's message loop and all is well. If that window handle is created on a background thread, messages sent to that window handle can only be retrieved by a message loop running in that thread.

Try this: Make one COM call from your main thread before making COM calls on the background thread. This will force COM to initialize on your main thread where you have a message loop. See if this unblocks the background COM calls. Just a thought.

dthorpe
  • 35,318
  • 5
  • 75
  • 119
  • I've tried adding a call to StaticCall on the main thread prior to creating the thread as described in the question. The StaticCall on the main thread returns fine, but the call on the thread still blocks. I'll look into your idea about pumping the message queue on the background thread. Thanks – Montgomery 'monty' Jones Jun 23 '11 at 08:00
2
foo.Start();

while (foo.IsAlive)
{
}

Yes, that's guaranteed deadlock. COM objects very often have thread affinity. They can tell COM that they are not thread-safe. The vast majority are not, just like .NET classes. COM then takes care of calling them in a thread-safe manner. Very unlike .NET, it leaves providing thread safety completely up to you.

You created the COM object on your main thread. So COM must make calls on the COM object on the main thread as well to make the safety guarantee. It cannot do that, your main thread is busy. Is looping on the foo.IsAlive property. Any calls made on the worker thread cannot complete until your main thread goes idle. Your main thread can't go idle until the worker thread completes. Deadlock city.

One fix is this:

foo.Start();
foo.Join();

While Thread.Join() is a blocking call as well, it actually works. The CLR implements it, it knows that blocking an STA thread is illegal. It pumps a message loop, that allows the COM calls to be marshaled and that allows the thread to complete.

Well, that's probably not what you want to do, you are doing something inside that loop. The only other thing you can do is calling Application.DoEvents(), that also pumps the message loop. That's dangerous, you have to disable the user interface so that the user cannot close the main window and cannot again start the thread.

This is otherwise the There Is No Free Lunch principle, you cannot magically make code that is not thread-safe support threading. There is no concurrency, the COM object methods still run on your main thread. And if they take a long time then they'll still freeze the UI.

Which leads to the other workaround, create the COM object on your worker thread instead. It must be STA and typically needs to pump a message loop. Example.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • So, replacing the while loop with foo.Join() solves the issue in my example, but I notice that, like Jalal, you have mentioned creating the COM object on the worker thread. I was under the impression that I was already doing this. Given that StaticCall creates the COM object and calls into it, what makes you think the COM object is being created on the main thread? Perhaps there is a fundamental point I am missing? – Montgomery 'monty' Jones Jun 23 '11 at 12:06
  • I didn't see a *new* statement in the snippet. Not enough code, I don't know why COM needs the message loop to marshal. Clearly it does. Use Debug + Windows + Threads to switch between threads and see where the worker is blocked. – Hans Passant Jun 23 '11 at 12:23
0

If the normal call -without start new thread- to that method does not have any problem, then it is mostly because you don't invoke that method from the same thread that the com object was created on. to create a custom invoking mechanism see this.

Community
  • 1
  • 1
Jalal Said
  • 15,906
  • 7
  • 45
  • 68