设为首页收藏本站Access中国

Office中国论坛/Access中国论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

返回列表 发新帖
查看: 8971|回复: 8
打印 上一主题 下一主题

[Access本身] Access菜鸟七大邪门武器之五:在VBA中简单使用多线程

[复制链接]
跳转到指定楼层
1#
发表于 2016-12-19 12:56:41 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 ganlinlao 于 2016-12-19 13:26 编辑

       兄弟,如果你和我一样是一个Access菜鸟,而且是属于无可救药的那种,那么在过去的很多年里,你一定听过很多很多的人不停地告诉你,VB6VBA是无法使用多线程的,以至于你忍不住相信多线程是VBA不可逾越的障碍。忍不住相信多线程对于VBA来说只是传说中的诗和远方,远方遥远得一无所有。而我们这样的菜鸟只剩下眼前单线程的苟且,并把这种苟且当成一种美好、快乐和满足。

是的,他们说的没错,VBA有着一颗易碎而脆弱的玻璃心。


在过去的十多年里,无数的人都试图在vba里使用多线程,但几乎都倒在淋漓的血泊和无尽的让人抓狂的自虐中。


今天让我们揭开在VBA中使用多线程神秘的面纱,让这个沐浴着阳光梳着长发的美少女,能对你轻轻回眸一笑。


今天的cpu绝大多数是双核双线程、双核4线程、四核四线程、四核八线程、八核……,所以在VBA中使用多线程来提高运行效率显得很有必要。而且在一些场景下,你也很渴望能使用多线程来改善惨不忍睹的状况,比如在excel中的多个大循环计算会让excel表很卡,大批量复制文件,下载网络数据……都很容易造成accessexcel界面出现“假死”。


     在VBA中,其实可以使用不少的线程库,比如vbthreadfacoryvbrichclient5中线程库……事实上你能找到不少的vb线程库,前面两种线程库是我了解到的比较稳定而且功能比较完备的线程库。今天我在这里介绍是另一种更加简单易用的线程库,虽然它功能不那么完备,但却是简单明了,用法简单直接,能让我们更容易理解多线程的概念和入门使用。

关于进程、线程的概念,你可以百度或详见我的空间日志。

      Accessexcel的主线程:Access.exe是一个进程,access.exe会为每一个打开的access文件(指的是客户端)分配一个线程(这里我们暂且称此线程是主线程),这个Access文件中所有的Form,控件都在同一个线程中运行。注意是所有的Form及其下面的控件!同样的,excel.exe会为每一个打开的workbook分配一个线程,这个workbook中所有的表(包括每一个表及其下面的userform及控件)都运行在同一个线程中。了解这一点蛮重要的,因为这能让我们更注意保护子线程的安全,减少accessexcel崩溃。

    Accessexcel的主线程消息处理:事件是一种消息,而消息是一种线性队列,所以所有子线程跟主线程的通讯,最终都会串口化成主线程的事件(消息),最终在主线程那里变成单线程的事件。主线程无法在同一时间点处理两个以上的事件,而这一点恰恰是accessexcel在使用多线程上最容易引起崩溃的原因。而且要注意,accessexcelvba处理消息能力很差(因为我们不知道access处理完一个事件之后会不会在内部自动触发另一个我们不知道的事件,据我所知,excel内部事件远远比vba能用到的事件多得多),相比较而言,编译过的vb6程序在多线程方面则要稳定得多了。


      如何避免让access在同一时间点处理两个事件,是我们使用vba多线程排在首位任务。任何引发accessexcel在同一时间点处理两个以上事件,accessexcel一定会崩溃


      在vba中使用多线程,以下是我个人简单的经验,以后你使用多了,也可以总结一下并写出来,让其他人少走一些弯路。


      经验1:在任何调试多线程代码之前,一定要先保存。先保存后调试是铁律,否则很多意外,会让你欲哭无泪。


      经验2:尽量减少子线程跟主线程的通讯次数。很多的时候,其实我们只需一个子线程跟主线程通讯而已(比如我们常常只需要显示一个进度条就够了)。如果进度条需要100次跟主线程通讯,那么想办法让它减少成只有10次,跟主线程的通讯次数越少,越能减少让access主线程同时处理两个消息的机会。


     经验3:要及时主动销毁子线程占用的资源。不能指望accessexcel自动帮你销毁。如果子线程占用资源没有销毁,会引发很多问题,甚至引起崩溃。所有一般对于子线程,我们尽量在需要时创建,用完立即销毁。

    经验4:如果是控件类,尽量用vb6编译成dll,然后再在access中使用,我前面提到过了,vb6编译过的dll在多线程方面要稳定得多了。如果是写很多需要多线程调用的函数,那用vb6编译成dll,也会更稳妥一些。

    经验5:当一个accessform不在激活状态,最好让它对应的子线程都处在休眠状态(挂起),当整个access客户端,不在激活状态,也最好让它对应的所有子线程都挂起。这样能大大减少让access同时处理两个消息的机会。这也同样适用excel。不要忘了,不管是access还是excel都是mdi多文档程序。
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏1 分享分享 分享淘帖 订阅订阅
2#
 楼主| 发表于 2016-12-19 12:57:05 | 只看该作者
本帖最后由 ganlinlao 于 2016-12-19 13:10 编辑

接下来,你只需要足够的耐心和谨慎,让我们开始地狱般的VBA多线程之旅。

VbMThread库介绍:

方法:

Function Create() As Boolean

创建子线程

Function Terminate() As Boolean

退出当前子线程

属性:

Property hThread As Long

返回子线程句柄,只读

Property Priority As ThreadPriorityConstants

设置子线程的优先级。

Property Suspended As Boolean

设置子线程是否休眠(挂起)状态

Property ThreadID As Long

返回子线程的线程ID,只读

事件:

Event AsyncProcedure(SyncCallback As IThreadSyncCallback)

异步调用函数或过程或类。

注:当主线程调用子线程时,自动触发此事件。

    任何在AsyncProcedure中,调用主线程的有关窗体、控件,都会引起崩溃。

Event  PriorityChanged(OldPriority As ThreadPriorityConstants, NewPriority As ThreadPriorityConstants)

事件:子线程优先级发生变化

Event SuspendedChanged()

事件:子线程休眠状态发生变化

Event SyncCallback(Argument)

事件:回调。主线程的回调函数。这个事件本质是event complete()

即AsyncProcedure调用的函数运行完后,触发SyncCallback ,并将argument参数回调给主线程。

注:有关主线程的窗体、控件的调用或引用,都只能在SyncCallback中。也可以通过主线程中介,让A子线程调用另一个B子线程,当然也有另外的方法。

SyncCallback接口:

只有一个方法

Raise(Optional ByRef Argument As Variant)

用来告诉主线程,函数运行完成。并将argument参数传给主线程。



这个线程库,只提供异步的方式,不提供线程同步。事实上,在vba中,我们也只能使用异步的方式,目前我还不知道如何使用同步的方式。在大多数的情况下,使用异步多线程,才能比较有效地提高效率。Vbrichclient的线程库同时提供了异步和同步的方式。



整个VBMThread线程库原理非常简单:即创建一个异步子线程,让子线程执行一段函数或过程,函数或过程执行完,通过argument参数将结果传给主线程,然后销毁子线程。

本线程库缺失三个重要的特性:

1、缺少timeout时,子线程强制退出。在运行多少秒无果后(比如异步查询sql server,一直连接不上),应该有强制退出的方法,这个特性蛮重要的。所以只能自己手动处理

2、缺少自定义消息趸,在使用类时(特别是带窗口的),自己构建自定义消息趸,会让程序控制粒度更细,稳定性更好。

3、错误处理:错误处理做得不够好。一般子线程运行出现错误,应该将错误回调给主线程,进行处理。所以你也只能手动进行错误回调。

注:msgbox函数是一个阻塞式函数,在子线程中调用msgbox函数,会引起包括主线程及所有其它子线程都挂起。
3#
 楼主| 发表于 2016-12-19 12:57:25 | 只看该作者
本帖最后由 ganlinlao 于 2016-12-19 22:37 编辑

多线程使用场景1:使用多个子线程运行函数。
例子中的函数很简单,只是为了展示一下 子线程如何运行一个函数。效果跟单线程没啥区别。
因为一个子线程运行一个函数或过程,无论在子线程中怎么循环,最终获得返回值,也只是跟主线程通讯一次。
所以安全性很高。要开多少个子线程,完全由你决定。
当然线程开得越多,性能反而会下降。所以一般cpu是几核,就开几个线程。其余的没必要多开。

附件:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
4#
 楼主| 发表于 2016-12-19 12:57:37 | 只看该作者
本帖最后由 ganlinlao 于 2016-12-21 09:12 编辑

使用场景二:多个子线程与主线程频繁交互通信。
比如说显示进度条,就是一个比较常见情况。
在这种情况下,VBMThread的不稳定性就很明显。相比之下,vbrichclient因为调用的是vb6编译过的dll函数,稳定性非常好。
使用vbmthread,尽量只使用一个子线程跟主线程频繁通信。vbrichlient则没有这个限制。


这个例子只是简单地模拟一下进度条。



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
5#
 楼主| 发表于 2016-12-19 12:57:51 | 只看该作者
本帖最后由 ganlinlao 于 2016-12-21 21:42 编辑

使用场景三:异步查询与Recordset

        多线程一般涉及到网络方面往往就能显示出巨大的优势,特别是异步。想想网页的ajax技术几乎是唯一的标配,就能知道异步对于网络的重要性了。
而在access中,如果你的数据库是在本地的Pc上,则多线程意义不大,特别是access的绑定技术非常的强悍,使用多线程反而增加无谓的不安全和性能下降。但如果是access+ SQL server这种网络型的,异步多线程,在某些方面则有明显的优势。
       应该说异步,能让界面不阻塞,从而使操作更流畅,虽然通过单线程的事件也能实现同样的功能,但使用异步还是更好一些。
      比如一些特殊场合:
      登陆界面的验证、注册时的用户名即时验证,下拉列表的逐字提示……使用异步效果会更好一些。
     
      在Access中不建议对主要的Recordset使用多线程,因为涉及到窗体的绑定,使用多线程反而不太好。而且根据以前很多使用c++的人多线程调用Recordset,都建议一个线程开一个connection,如果共用一个connection,会不定地出现问题。而且据说Recordset在多线程场合下有bug。
我在这里指的是多线程调用Recordset,并要最后进行数据更新。如果只是查询,那是另外一回事了。
    VBMthread可以在线程间传递Recordset。但VBrichclient似乎传递不了,不知道VBrichclient的线程库是不是没有实现Istream接口,至少到目前为止,我还没有找到传递Recordset的方法。如果你找到方法了,希望告知一下。
      

6#
 楼主| 发表于 2016-12-19 13:13:38 | 只看该作者
占位
回复

使用道具 举报

点击这里给我发消息

7#
发表于 2016-12-19 13:20:16 来自手机 | 只看该作者
Access的黑科技来了
来自: 微社区

点击这里给我发消息

8#
发表于 2016-12-19 16:48:13 | 只看该作者
大神来了,处理数据的能力提高了很多,速度快的不要不要的。

点击这里给我发消息

9#
发表于 2016-12-23 13:54:59 | 只看该作者
这个牛啊
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|站长邮箱|小黑屋|手机版|Office中国/Access中国 ( 粤ICP备10043721号-1 )  

GMT+8, 2024-5-14 22:05 , Processed in 0.116002 second(s), 33 queries .

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表