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

问题描述 投票:0回答:3

我有一个调用 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不会返回,线程会无限期阻塞。

什么可能导致此行为?

附加信息:

  • 设置线程的公寓状态没有区别。
  • Visual Studio 输出窗口中的最后一条消息是 COM 互操作已加载的通知。
  • 对 COM 的所有调用都隔离在一个线程上。
.net multithreading com-interop
3个回答
3
投票

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

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

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


2
投票
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
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.