在COM暴露的程序集中进行线程化的正确/最佳方法是什么?

时间:2011-01-05 14:08:42

标签: vb.net multithreading com

首先让我说:我对VB.NET仍然缺乏经验,所以可能有一些我想念的简单,或者至少那是我所希望的。

我一直在编写一个COM公开的包装类来提供对旧版VB6应用程序中的Web服务的访问。我得到了一切正常工作 - 所有COM属性,方法和事件都出现在VB6应用程序中 - 但是有一个小细节:当我使用一种调用服务的方法时,它显然只是工作异步,所以我无法更新进度条或在等待相应的程序集事件触发时执行任何其他操作。

所以我得出结论,我需要使用一个单独的线程来访问Web服务。我决定最干净的方法是通过一个单独的类来做到这一点,它在主包装类中引发事件,这反过来又引发了公共COM事件。以下是我实施它的方法:

在包装类的顶部,我把:

Private WithEvents oService As Service_Class
Private ServiceThread As Thread

然后,在其中一个主要的包装方法(比如简单的状态请求)中,我提出以下内容:

oService = New Service_Class
ServiceThread = New Thread(AddressOf oService.RequestStatus)
ServiceThread.Start()

一旦服务调用获得了'Status'值,它就会在主包装器中引发StatusReport事件,从而引发COM暴露的StatusReport事件,因此:

Private Sub oService_StatusReport(ByVal Status As String) Handles oService.StatusReport
    RaiseEvent StatusReport(Status)

这一点很有效 - 它肯定不再占用调用应用程序中的处理 - 但似乎仍然存在问题:如果我尝试访问包装器中的任何控件,则调用应用程序崩溃对象的事件。我用.NET表单对它进行了测试,它没有崩溃,但它引发了“跨线程操作”错误。当然我可以在调用应用程序中使用计时器,监视变量或其他东西,以解决这个问题,但这看起来非常混乱,让我觉得我可能正在做一些根本错误的方式我正在实现穿线。有人可以告诉我我做错了什么以及如何解决它吗?

2 个答案:

答案 0 :(得分:2)

VB6完全不支持 支持线程。如果你仔细编程但是在VB6运行时出现了问题,它可以在VB5中运行。只要VB6代码永远不会看到它,就可以在.NET代码中使用线程。你必须编组事件,将调用调用到VB6创建类对象的STA线程。

使用Control.BeginInvoke()或Dispatcher.BeginInvoke()完成。如果您没有表单或调度程序来进行此调用,则创建一个隐藏的Winforms表单,以便您可以使用它进行编组。做到这一点并不容易。

答案 1 :(得分:1)

是。这是Windows窗体应用程序中的常见问题,在本机Windows应用程序中也是如此(例如,使用C / C ++编写的针对本机Windows API的应用程序)。你在这里有不同的部分,但这是同样的问题。

让我给你一些背景知识:

首先,您需要知道.NET事件只是一个方便的代理包装,旨在使您可以轻松定义类中 其他将实现的方法,以及您的班级将致电“。请注意该方法与“普通”方法之间的区别,即“您的类实现和其他将调用”。关键的观察是事件对线程一无所知。

如果您考虑一下,您会意识到VB6事件也是如此。

你需要知道的第二件事是VB控件,VB6使用的控件,以及.NET的Windows.Forms世界(以及WPF)中的控件受所谓的线程关联性<的约束/ em>:只能从创建它们的线程访问它们。

请注意,表单(Windows / VB6和.NET表单)都是控件本身(虽然非常复杂),因此对于Forms来说都是如此。

将两个事实放在一起......你会立即看到发生了什么。当您的工作线程代码引发事件时,它只是直接调用事件侦听器方法,而它是从错误的线程执行的。 VB6中的事件监听器方法(从工作线程而不是VB6主线程运行)触及VB6表单中的任何Windows控件,“ puff ”。

在.NET Windows.Forms世界中,微软为您做了所有非常努力的工作。

每个Control对象(包括Form对象)都带有一个名为Invoke()的方法。如果工作线程必须与表单/控件进行通信,则不会直接操作控件。相反,托管控件的表单必须实现适当的委托,而工作线程使用该委托作为参数调用表单的Invoke()方法,并可能调用一些数据参数。 Control.Invoke()然后处理所有线程混乱,并确保从创建表单及其控件的线程调用委托。最后,表单实现的委托方法负责代表工作线程操作表单的控件。

直接从工作线程调用委托方法而不是使用Control.Invoke()方法是一种崩溃应用程序的可靠方法。

现在,我承认:当我读到你的问题时,我的直接反应是“是的,我确切地知道发生了什么;我只会解释......”然后我意识到我只是对如何.NET框架实际上在内部处理它。然后我想了一些,并意识到这根本不是一项简单的任务。

没问题!我刚刚在 Reflector 中加载了System.Windows.Forms.DLL来快速检查Control.Invoke()的执行情况......感觉所有的血都从我脸上消失了。那里有一些严重的 ninjitzu 。来吧,看看你自己。

当你完成后,你会理解汉斯建议的为什么 - 这也是我的建议:

创建隐藏的.NET表单。请确保new来自VB6使用的线程的表单。使用我上面描述的方法,让工作线程通过Invoke()以隐藏的形式调用委托。然后代表可以举起你的活动。然后,Everyhing将正常工作。

我知道。创建一个隐藏的窗口只是为了改变用于调用函数的线程,这听起来很疯狂;但事实并非如此。您应该知道,完全 COM如何处理VB6样式的COM对象(称为STA对象)和多线程COM对象之间的通信。

额外的好处是否值得麻烦,只有你可以决定。

祝你好运。

参考文献:

  • Control.Invoke() {{1}}在VB.NET中有一个示例,可以快速提炼到您需要的内容。如果您在解析示例时遇到困难,请在此响应中添加注释,我会尝试提供帮助。