再谈VB中的多线程

 前些日子无意中又看到了Matt大师写的VB中使用多线程的文章。虽然此前看了不下十遍,但仍然摸不着头脑,一直都没试过。这回决定先看看他的源码,会不会有什么新的发现。

下载了源码,里面不管,先编译运行下看看。按下按钮,等待片刻后同时弹出了三个对话框,进程管理器里VB进程的线程数也果然增加了3,测试了好几下,程序毫无崩溃的迹象。确实如同他所说的那样安全!

用VB写过多线程的应该知道,在线的线程里MsgBox,程序会立即崩掉。不仅如此,只要是会阻塞线程的操作都会引起崩溃,最典型的便是Sleep API。后来还发现Set语句,声明固定数组,Hex、Left之类的内置函数也会崩溃。如此多的限制可能只能让VB在新的线程里做做加减乘除了。所以先前试了再试,最后还是放弃了使用新线程的念头。

看到此程序能如此顺利的运行,决定这回非拿下不可!毕竟如果能用多线程,先前的好多程序都能够大大改进了。Matt大师作为VB的开发人员,当然知道这其中的关键之处。不过现要从这么多满满的篇幅中找出这其中的秘密,又有的摸索了。

先大致的看了遍,想了下,既然他的程序中能正常跳出Msgbox,那我就把Msgbox的位置提前,看看究竟放在哪个地方的时候程序开始不能正常运行。经过一番测试,发现在新线程回调函数中,在CoCreateInstance()那句前Msgbox程序又如往常那样立即崩溃,之后便一切正常!看来这些秘密就隐藏在这些Co开头的API中了。那其他模块中那些大幅度代码没有作用吗?不清楚,但可以一句一句的注释的掉,看看能不能正常的运行。

测试了大半天,几乎所有的代码都被注释了,但程序依旧可以运行(现在只是可以,不是正常了,毕竟程序相关的功能也注释掉了),三个对话框也能弹出一次。这时其中的秘密毫无保留的显现出来了。整理了下代码,其实所谓的秘密无非就是3个API大调用——CoInitialize,CoCreateInstance和CoUninitialize。

VB是COM的天下,不调用CoInitialize和CoUninitialize来初始化COM,新线程里的一些代码当然就无法运行了。不过就这两个API还是不够的,最最关键的秘密就在于CoCreateInstance之中,至于为什么要在这里使用这个函数,现在仍是困惑。

这个API的VB类型库定义如下:

Function CoCreateInstance(CLSID As VBGUID, pUnkOuter As Unknown, dwClsContext As CLSCTX, riid As VBGUID, pvObj As IUnknown) As Long

在这个程序里,CLSID必须是当前ActiveX工程中公共类的CLSID,pUnkOuter为Nothing,第三个CLSCTX_INPROC_SERVER,第四个创建好的对象,这与一般情况创建ActiveX一样。

多线程算是成功的运行起来了,但各种麻烦也开始出现。在现在看来,各种各样的错误正是由这个CoCreateInstance引起的。CoCreateInstance当前工程中的类时,会导致工程重新加载一遍,选择Main()函数启动的话会发现里面的对话框会弹出两次。这就给模块级的变量提出了个题,前后两次的加载不会不引起冲突?事实上这个工程里根本不能使用模块级的变量,我回顾头来看了下Matt原先的代码,果然是没有模块级的变量的,新线程里用到的数据都应该用参数传递进去。

到了这一步,线程的创建算是告成,此时连续弹出个几十个对话框也没问题。然而,对话框的弹出仅仅是个简单的测试而已,最终的要在新线程里运行的代码各不相同,所以还要提供一个接口,取名为Runnable。在新的线程运行前将此接口通过CoMarshalInterThreadInterfaceInStream写入流中,新线程创建后再通过CoGetInterfaceAndReleaseStream取出这个接口,然后调用接口的Run方法,即可运行客户端的代码;客户端只需将类实现Runnable,将需要在新线程里运行的代码放入Runnable_Run中即可。

看似是个不错的想法,但在实际运行中又出现了新的问题!由于VB的对象都是单线程公寓模型(STA)的,所以无论有多少个线程,一次只能让其中的一个线程访问某个对象,其他的线程都被挂起。所以在客户端测试时,新线程的MsgBox运行了,原先的Form窗体却失去的相应,就效果而言,跟直接从Form中跳出对话框没什么区别!如此的多线程就没有什么并行度可说了,即使运行的很稳定;更为严重的是,若客户端Implements Runnable的是个类而不是窗体(窗体也可以Implements接口)的话,那在线程工程里Runnable接口根本就不能被写入到流中,提示“私有的对象只能在当前的工程里使用”的错误。

实现了却无法应用?!经过几天迷茫的测试,发现这个问题源于CoMarshalInterThreadInterfaceInStream和CoGetInterfaceAndReleaseStream。正是这两个接口调度的API,保证了VB的STA机制能够正常的运行。如果绕过这个机制,也许想像中的就能实现。

于是放弃接口调度,使用ObjPtr(Runnable接口)作为参数传递到新的线程里;新线程中使用CopyMemory把这个地址复制到一个空的Runnable接口上,然后在调用其Run方法,完成后再CopyMemory数字0到此接口,当作这个类什么也没出现过。这一次,倒是可以了,MsgBox跳出后主窗体也可以相应事件了,不过Msgbox的标题变成了“错误”两字?!

莫名其妙,这两个字从何而来?正常情况下Msgbox标题显示的应该是工程的名字,莫非是App这个对象出问题了?一试,果然,访问App中的任何一个成员都会崩溃!久违的崩溃又出现了~~~不过在主线程用定义一个引用到App上的对象,然后在新线程中访问倒是没问题。不过另一个更为致命的是新线程里居然不能实例化工程中的类~~郁闷+无奈。调试了都快两个星期了,居然仍有这样的错误。。。不想继续了,只要是新的线程中不要太复杂,一般的程序都不会有问题。

看来先前所说VB不支持多线程没错,不过也不全对,至少有好多程序由此可以改进了。希望以后能有更好的办法完善VB的线程!

你可能感兴趣的:(再谈VB中的多线程)