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

cl25kdpy  于 2023-08-08  发布在  .NET
关注(0)|答案(3)|浏览(110)

我有一个C#静态函数,它调用COM组件。
当从一个简单的WPF应用程序调用这个函数时,它会正确返回。代码可能看起来像这样:

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

字符串
当我调用这段代码时,变量将被设置,调试消息将按预期输出。
但是,如果我将相同的调用 Package 在线程中,它将永远不会返回。失败的代码可能看起来像这样:

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

while (foo.IsAlive)
{
}


在这种情况下,StaticCall将不会返回,线程将被无限期阻塞。
是什么导致了这种行为?
附加信息:

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

4dc9hkyq1#

我似乎记得COM需要在使用COM互操作的线程中运行一个活动消息循环。我不知道.NET的COM互操作实现的细节,但在很好的旧Win32中,如果你试图在完成所有特殊的初始化步骤后对COM进行进程间调用,它仍然会冻结在COM调用处,就像你描述的那样。在后台线程中添加一个简单的peekmessage循环,调用就会通过。也许你需要在.NET代码中做一些类似的事情?
背景:COM进程间通信依赖于SendMessage通过COM创建的窗口句柄。如果该窗口句柄是在主线程上创建的,那么发送到该窗口句柄的消息将由主线程的消息循环处理,一切正常。如果该窗口句柄是在后台线程上创建的,则发送到该窗口句柄的消息只能由该线程中运行的消息循环检索。
试试这个:在后台线程上进行COM调用之前,先从主线程进行一次COM调用。这将强制COM在主线程上初始化,在主线程上有一个消息循环。看看这是否解除了对后台COM调用的阻塞。只是一个想法。

yfwxisqw

yfwxisqw2#

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对象方法仍然在主线程上运行。如果他们花了很长时间,那么他们仍然会冻结UI。
这会导致另一种解决方法,即在辅助线程上创建COM对象。它必须是STA,并且通常需要泵送消息循环。Example的数据。

knsnq2tg

knsnq2tg3#

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

相关问题