2011-06-22 39 views
1

我有一个调用COM组件的c#静态函数。从.Net调用COM调用挂起线程

当从一个普通的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将不会返回并且线程无限期地被阻塞。

什么可能导致此行为?

附加信息:

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

* foo.Start()之后的代码是什么?这很重要。主线程不会闲置时,死锁很常见。 –

+0

@Hans我编辑了这个问题来添加这个细节。 –

回答

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,通常需要泵送消息循环。

+0

因此,用foo.Join()替换while循环解决了我的例子中的问题,但我注意到,就像Jalal一样,您提到了在工作线程上创建COM对象。我的印象是我已经这样做了。鉴于StaticCall创建COM对象并调用它,是什么让你认为COM对象正在主线程上创建?也许我缺少一个基本点? –

+0

我在代码片段中没有看到* new *语句。没有足够的代码,我不知道为什么COM需要消息循环来编组。显然它确实如此。使用Debug + Windows +线程在线程之间切换并查看工作人员被阻止的位置。 –

3

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

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

试试这个:在后台线程上进行COM调用之前,从主线程发出一个COM调用。这将强制COM在你的主线程上初始化你的消息循环。看看这是否会阻止后台COM调用。只是一个想法。

+0

我试着在问题描述中创建线程之前在主线程上添加对StaticCall的调用。主线程上的StaticCall返回正常,但线程上的调用仍然阻塞。我会研究一下在后台线程上抽取消息队列的想法。谢谢 –

0

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

+0

“所有对COM的调用都被隔离在一个线程上。”通过这个我的意思是COM对象都创建并调用同一个线程。 –