从.Net线程调用时,COM调用挂起

时间:2011-06-22 16:26:07

标签: .net multithreading com-interop

我有一个调用COM组件的c#静态函数。

从普通的WPF应用程序调用此函数时,它会正确返回。 代码可能如下所示:

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

当我调用该代码时,将设置该变量并按预期输出调试消息。

但是,如果我在一个线程中包装相同的调用,它永远不会返回。失败的代码可能如下所示:

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

while (foo.IsAlive)
{
}

在这种情况下,StaticCall将不会返回并且线程被无限期阻塞。

可能导致此行为的原因是什么?

其他信息:

  • 设置线程的appartment状态没有区别。
  • visual studio输出窗口中的最后一条消息是已加载COM互操作的通知。
  • 所有对COM的调用都在一个线程上被隔离。

3 个答案:

答案 0 :(得分:3)

我似乎记得COM需要在使用COM互操作的线程中运行的活动消息循环。我不知道.NET的COM互操作实现的细节,但是如果你在完成所有特殊的初始化步骤之后尝试在COM上进行进程间调用,那么在旧的Win32中,它仍会在COM调用时冻结,就像你描述的那样。在后台线程中添加一个简单的peekmessage循环,调用将通过。也许在.NET代码中需要做类似的事情?

背景:COM进程间通信通过COM创建的窗口句柄依赖于SendMessage。如果在主线程上创建了该窗口句柄,那么发送到该窗口句柄的消息将由主线程的消息循环处理,一切都很好。如果在后台线程上创建了该窗口句柄,则只能通过在该线程中运行的消息循环来检索发送到该窗口句柄的消息。

试试这个:在后台线程上进行COM调用之前,从主线程进行一次COM调用。这将强制COM在您拥有消息循环的主线程上初始化。看看这是否解除了后台COM调用的阻塞。只是一个想法。

答案 1 :(得分:1)

foo.Start();

while (foo.IsAlive)
{
}

是的,这确保了死锁。 COM对象通常具有线程关联性。他们可以告诉COM他们不是线程安全的。绝大多数都不是,就像.NET类一样。 COM然后负责以线程安全的方式调用它们。与.NET非常不同,它完全由您提供线程安全性。

您在主线程上创建了COM对象。因此COM必须对主线程上的COM对象进行调用以确保安全性。它不能这样做,你的主线程很忙。循环foo.IsAlive属性。在主线程空闲之前,对工作线程进行的任何调用都无法完成。在工作线程完成之前,主线程不能空闲。死锁城市。

一个解决方法就是:

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

虽然Thread.Join()也是一个阻塞调用,但它确实有效。 CLR实现它,它知道阻塞STA线程是非法的。它抽取一个消息循环,允许COM调用被封送并允许线程完成。

嗯,这可能不是你想要做的,你在这个循环中做了一些事情。您可以做的唯一其他事情是调用Application.DoEvents(),它也会消息循环。这很危险,您必须禁用用户界面,以便用户无法关闭主窗口,也无法再次启动该线程。

否则就是没有免费午餐原则,你不能神奇地制作非线程安全支持线程的代码。没有并发性,COM对象方法仍在主线程上运行。如果他们需要很长时间,那么他们仍会冻结用户界面。

这导致了另一种解决方法,而是在工作线程上创建COM对象。它必须是STA,通常需要泵送消息循环。

答案 2 :(得分:0)

如果正常调用-without启动新线程到该方法没有任何问题,那么主要是因为您没有从创建com对象的同一线程调用该方法。要创建自定义调用机制,请参阅this