关于PowerBuilder多线程的一些心得

来源:互联网 发布:行业经济数据2010 编辑:程序博客网 时间:2024/06/08 01:02
最近在看.Net多线程的时候突然想到PowerBuiler是否支持多线程开发呢?于是Google了下,原来真的可以,不过比较遗憾的是这关于PB多线程方面的资料无论是英文的还是中文的都十分稀缺,完全没有详细的资料可查,连官网都一样,PB自带的帮助文档就更不用说的,简陋啊!!于是自己摸着石头过河,其中也遇到不少莫名其妙的问题,但多数是IDE本身语义检测的缺陷,谁让这不是VS呢?...

----------- 正题分割线 -----------

在PB里要使用多线程开发应用程序必须的3个系统函数:

SharedObjectRegister(string classname,string instancename):

函数功能:共享一个以classname为类型名称的对象,并产生一个新的线程实例,名为instancename.

返回值:枚举类型,Success!表示成功,其他的可以F1.

SharedObjectGet(string instancename,objectinstance):

函数功能:获取SharedObjectRegister创建的线程对象实例的引用,并赋值给objectinstance变量.*objectinstance类型是instancename注册时的类型或继承自它.如果获取不成功,将不对objectinstance操作.

返回值:同上.

SharedObjectUnRegister(string instancename):

函数功能:注销线程实例名为instancename的子线程.

返回值:同上.

还有一个函数SharedObjectDirectory(string instancenames[]{,string classnames[]})函数,得到所有注册的实例名和注册的类型.

PB的多线程有很多限止,以下几个目前发现需要注意的地方:

1.主线程与子线程的资源是相互独立的,子线程可以访问在程序中定义的全局变量,但在线程中对其做的任何修改将不对主线程产生影响.如果有一个全局的引用类型的变量,虽然在主线程中已经实例化但在子线程中访问的时候它是没有被实例化的,这就是它们资源相互独立的地方,主线程与子线程都有相同的全局变量定义,但其作用域只在它们各自的线程内,子线程与子线程之间也是一样,就算是同一个对象里定义了Shared Variables,注册为不同的线程实例,它的资源也是相互独立的,这一点非常的重要.

2.主线程与子线程通讯只能通过NonVisualObject类型或继承自NonVisualObject类型的实例来传递数据,而且传递的数据只能是基本类型的数据,不能传递引用类型的数据,如datastore,transaction等.所有的通讯都只能在这个NonVisualObject类型的中间对象处理,NonVisualObject类型是运行在主线程的.引用类型在哪个线程里Create就是哪一个线程中的资源.

3.操作子线程时,如果是异步调用则必须在函数调用前加Post关键字,如果不加则是同步调用.

4.如果子线程正在执行函数,那么此时不能同步对子线程进行操作,否则将会使线程阻塞造成死锁,所以一定要处理好线程间的资源同步与互斥,这就是为什么有很多人抱怨PB多线程容易使程序挂掉的关键所在.

5.使用SharedObjectUnRegister将线程注销后不会销毁注册的对象实例,而那个对象实例仍然是工作在子线程上过的,不过不能再用SharedObjectGet获取到该线程名的实例引用,但此时可以以这个名字重新注册它,所以当你不再使子线程时应该及时销毁其实例对象以回收内存.

6.子线程Halt了主线程不会退出,相反主线程Halt子线程会退出.

7.SharedObjectRegister注册后的线程可以在程序的任何位置获取,线程名必须是唯一的.

----------- Sample code -----------

//将w_ThreadTest窗口设为回调对象的父对象 *明义上QueryCallBack.of_SetParent(Parent)新建6个NonVisualObject对象:nvo_ThreadBase//用于创建线程的基类对象.nvo_ExpThread//导出线程,继承自nvo_ThreadBasenvo_QueryThread//查询线程,继承自nvo_ThreadBasenvo_CallBackBase//线程的回调基类对象nvo_ExpCallBack//导出线程回调对象,继承自nvo_CallBacknvo_QueryCallBack//查询线程回调对象,继承自nvo_CallBack/*用于主线程与子线程间数据交互.*一个回调对象可以为多个线程共享,但不建议这么做因为可能会有一些不可预知的事情发生,我推荐一个线程对应一个回调对象,但回调对象可以有多个父对象*///----------nvo_CallBackBase-------//添加以下实例变量:Private:window iw_Parent//回调用的父对象,使用window类型是因为这样可以使用多态编写通用代码boolean ib_ThreadExecuted//线程状态标志添加以下函数:Public (none) of_SetParent(window aw_parent)iw_Parent = aw_parentPublic boolean of_IsExecuted()return ib_ThreadExecutedProtected (none) of_Dispose()//释放资源添加以下自定义事件:(none) ue_ThreadStart(string ThreadName)ib_ThreadExecuted = trueif IsValid(iw_Parent) then iw_Parent.Dynamic Event(ThreadName,this)(none) ue_ThreadStop(string ThreadName)ib_ThreadExecuted = falseif IsValid(iw_Parent) then iw_Parent.Dynamic Event(ThreadName,this)of_Dispose()(none) ue_ThreadError(string ThreadName,string ErrorText)if IsValid(iw_Parent) then iw_Parent.Dynamic Event(ThreadName,this,ErrorText)(none) ue_ThreadMsg(string ThreadName,string Msg)if IsValid(iw_Parent) then iw_Parent.Dynamic Event(ThreadName,this,Msg)//------------------------////----------nvo_ExpCallBack(继承自nvo_CallBackBase)-------///*没有要添加的功能,纯粹为了更优雅.*以后可以扩展功能*///------------------------////----------nvo_QueryCallBack(继承自nvo_CallBackBase)-------//添加以下实例变量:Private:Datawindow idw_QueryTarget//回调填充对象添加以下函数:Public (none) of_SetQueryTarget(datawindow adw_Target)idw_QueryTarget = adw_TargetPublic integer of_SetFullState(ref blob abb_data)int li_retif Not IsValid(idw_QueryTarget) then return -1idw_QueryTarget.SetRedraw(False)idw_QueryTarget.Event RetrieveStart()li_ret = idw_QueryTarget.SetFullState(ref abb_data) //填充数据idw_QueryTarget.Event RetrieveEnd(idw_QueryTarget.RowCount())idw_QueryTarget.SetRedraw(True)return li_ret/*由于SetFullState会将目标DW的transobject也覆盖了,并且还会将DW的Select语句覆盖所以填充之后要记得将其transobject和Select设回原来的值!*///------------------------////----------nvo_Thread-------//添加以下实例变量:Protected:string is_ThreadName="子线程" //线程名boolean ib_Executed //状态标志添加以下函数:Public (none) of_SetThreadName(string as_name)is_ThreadName = as_nameProtected (none) of_Task()//处理的任务Public (none) of_Start()//启动线程Public (none) of_Stop()//停止线程ib_Executed = false Protected (none) of_Dispose()//释放资源//------------------------////----------nvo_ExpThread(继承自nvo_Thread)-------//添加以下实例变量:Private:nvo_ExpCallBack CallBack//线程回调对象Datastore ids_Exp//导出数据用的临时存放的DS添加以下函数:Public (none) of_SetCallBack(nvo_ExpCallBack an_CallBack)CallBack = an_CallBackPublic (none) of_DataExp(ref blob abb_data)/*因为子线程不能操作主线程的引用类型变量,所以只能将数据直接传过来.这种拷贝Datawindow或Datastore数据的方法相对于如object.datawindow.Data取字符串的方式要快4~5倍的样子(实测),而且因为这个用GetFullState()取出的二进制数据里还包括了对象定义等信息,所以下面就不用定义ids_Exp的DataObject什么的了.*/if Not IsValid(ids_Exp) thenids_Exp = Create Datastoreend ifids_Exp.SetFullState(ref abb_data)重写父对象的函数:Protected (none) of_Task()//重写父对象的of_Taskif IsValid(Datastore) thenYield()//先执行完消息队列中的函数*就是执行外部后面Post进来的函数if Not ib_Executed then //如果被外部停止则退出函数,这样可以做线程取消功能returnend ifDatastore.SaveAs()//可以用OLE或其它方面处理导出的数据,这里只用SaveAs为演示elseCallBack.Event ue_ThreadError(is_ThreadName,"Datastore未实例化!")returnend ifPublic (none) of_Start()//启动线程if ib_Executed then returnif Not IsValid(CallBack) then/*注释:2012-04-18这在子线程创建Callback对象只是为了保证如果调用方未设置Callback,也能够正常工作当然这里也可以直接return*/CallBack = Create nvo_ExpCallBackend ifib_Executed = trueCallBack.Event ue_Start(is_ThreadName)of_Task()ib_Executed = falseCallBack.Event ue_Stop(is_ThreadName)of_Dispose()Protected (none) of_Dispose()Destroy ids_Exp//------------------------////----------nvo_QueryThread(继承自nvo_Thread)-------//添加以下实例变量:Private:nvo_QueryCallBack CallBack//线程回调对象string is_Dataobject//查询对象的Dataobject添加以下函数:Public (none) of_SetCallBack(nvo_QueryCallBack an_CallBack)CallBack = an_CallBackPublic (none) of_SetDataObject(string as_dataobject)is_Dataobject = as_dataobject/*这里假设是单事务的情况下,否则你可能需要记录提供采用什么事务来进行数据查询因为在子线程中是不能使用主线程中已有的事务的,比如SQLCA也不行.*/重写父对象的函数:Protected (none) of_Task()//重写父对象的of_Taskif is_Dataobject<>"" thenYield()//先执行完消息队列中的函数*就是执行外部后面Post进来的函数if Not ib_Executed then //如果被外部停止则退出函数,这样可以做线程取消功能returnend ifTransaction lts_TransDatastorelds_Queryblob lbb_Datalts_Trans = Create Transaction/*设置事务信息*/Connect Using lts_Trans;lds_Query = Create DatastoreDatastore.SetTransObject(lts_Trans)Datastore.Dataobject = is_Dataobjectlds_Query.Retrieve()Yield()if Not ib_Executed then returnend iflds_Query.GetFullState(ref lbb_Data)if CallBack.of_SetFullState(ref lbb_Data) = -1 thenCallBack.Event ue_ThreadError(is_ThreadName,"填充数据失败!")end ifDisconnect Using lts_Trans;Destroy lds_QueryDestroy lts_TranselseCallBack.Event ue_ThreadError(is_ThreadName,"is_Dataobject为空!")returnend ifPublic (none) of_Start()//启动线程if ib_Executed then returnif Not IsValid(CallBack) then/*注释:2012-04-18这在子线程创建Callback对象只是为了保证如果调用方未设置Callback,也能够正常工作当然这里也可以直接return*/CallBack = Create nvo_QueryCallBackend ifib_Executed = trueCallBack.Event ue_Start(is_ThreadName)of_Task()ib_Executed = falseCallBack.Event ue_Stop(is_ThreadName)of_Dispose()//------------------------//新建1个Window窗口:w_ThreadTest//测试窗口//---------w_ThreadTest-------//添加以下实例变量:nvo_ExpThread ExpThread//导出线程nvo_QueryThread QueryThread//查询线程nvo_ExpCallBack ExpCallBack //导出线程回调对象nvo_QueryCallBack QueryCallBack //查询线程回调对象添加以下自定义事件:(none) ue_ThreadStart(string ThreadName,nvo_CallBackBase CallBack)/*线程任务开始,在此添加响应代码ThreadName线程名CallBack回调对象*/Messagebox("["+ThreadName+"]"+"提示","线程任务开始")(none) ue_ThreadStop(string ThreadName,nvo_CallBackBase CallBack)/*线程任务结束,在此添加响应代码ThreadName线程名CallBack回调对象*/Messagebox("["+ThreadName+"]"+"提示","线程任务结束")(none) ue_ThreadError(string ThreadName,nvo_CallBackBase CallBack,string ErrorText)/*线程错误,在此添加响应代码ThreadName线程名CallBack回调对象ErrorText错误信息*/Messagebox("["+ThreadName+"]"+"错误",ErrorText,Stopsign!)(none) ue_ThreadMsg(string ThreadName,nvo_CallBackBase CallBack,string Msg)/*线程发来的信息,在此添加响应代码ThreadName线程名CallBack回调对象Msg信息*/Messagebox("["+ThreadName+"]"+"消息","Msg")在以下窗口Close事件中添加代码:if IsValid(ExpThread) thenSharedObjectUnRegister("ExpThreadInstance")Destroy ExpThreadDestroy ExpCallBackend ifif IsValid(QueryThread) thenSharedObjectUnRegister("QueryThreadInstance")Destroy QueryThreadDestroy QueryCallBackend if添加一个数据窗口dw_Test添加一个按钮cb_QueryThread,在其Clicked事件下写:if Not IsValid(QueryThread) then//用nvo_QueryThread类型注册一个查询线程实例,名为QueryThreadInstanceif SharedObjectRegister("nvo_QueryThread","QueryThreadInstance") <> Success! thenMessagebox("错误","创建线程失败!",Stopsign!)returnend if//获取一个名为QueryThreadInstance的线程实例引用,并赋给QueryThread *QueryThread的类型一定要是QueryThreadInstance注册时的类型是继承自那个类型if SharedObjectGet("QueryThreadInstance",QueryThread) <> Success! thenMessagebox("错误","获取线程失败!",Stopsign!)returnend ifQueryThread.of_SetThreadName("查询线程")end ifif Not IsValid(QueryCallBack) then//创建查询回调对象实例QueryCallBack = Create nvo_QueryCallBack//将w_ThreadTest窗口设为回调对象的父对象 *明义上QueryCallBack.of_SetParent(Parent)//添加用于查询线程返回数据填充的DW窗口QueryCallBack.of_SetQueryTarget(dw_Test)/* 以下代码于2012-04-12纠正 */QueryThread.of_SetCallback(QueryCallBack)end ifif QueryCallBack.of_IsExecuted() then/*如果查询线程正在执行则停止它此时只能用Post向线程发送消息,如果直接使用QueryThread.of_Stop()将会造成线程死锁*/QueryThread.Post of_Stop()//停止线程任务returnend if//因为此时查询线程未在执行任务,所以可以用同步的方式调用其上的函数QueryThread.of_SetDataObject(dw_Test.Dataobject)//此时用Post将消息添加到查询线程的消息队列上,并将控制权交会主线程,做到异步执行,也就是让查询线程在后台执行of_Start()函数QueryThread.Post of_Start()添加一个按钮cb_ExpThread,在其Clicked事件下写:blob lbb_dataif Not IsValid(ExpThread) thenif SharedObjectRegister("nvo_ExpThread","ExpThreadInstance") <> Success! thenMessagebox("错误","创建线程失败!",Stopsign!)returnend ifif SharedObjectGet("ExpThreadInstance",ExpThread) <> Success! thenMessagebox("错误","获取线程失败!",Stopsign!)returnend ifExpThread.of_SetThreadName("导出线程")end ifif Not IsValid(ExpCallBack) thenExpCallBack = Create nvo_ExpCallBackExpCallBack.of_SetParent(Parent)/* 以下代码于2012-04-12纠正 */ExpThread.of_SetCallback(ExpCallBack)end ifif ExpCallBack.of_IsExecuted() thenExpThread.Post of_Stop()//停止线程任务returnend if//将DW中的数据以二进制形式导出,放在lbb_data中dw_Test.GetFullState(ref lbb_data)//将lbb_data传给导出线程ExpThread.of_DataExp(ref lbb_data)//开始线程ExpThread.Post of_Start()//------------------------//

------------- 结束 -----------
上面的代码全部纯手敲,未经过PB环境调试,不过应该没什么问题,除非哪里打漏了.这里只是提供一个思路,可以在此基础上增加增量查询,过滤查询,多表查询,多表导出什么的,都是完全可以的!

其实PB的多线程功能对于数据库开发来说已经基本够用了,只要适当的变通一下.

原创粉丝点击