我有一个接口程序A,是exe的.其中一个发送消息的对象proxy
一个外部的Active Exe程序(B),引用这个发消息的对象,并订阅了该对象的事件.
B通过这个proxy对象发送消息之后,A程序会对B发送事件.通常情况下,这种发送是一对一的,可是在群发的时候,A对B发送的事件会很多.这就早成A发送事件1的方法还没有结束,A又Raise了一个事件2.
发送事件的方法如下:
public sub DoRaiseEvent
log "DoRaiseEvent begine with id<" & id & ">"
RaiseEvent OnMsg(TaskID, Info.InfoType)
log "DoRaiseEvent end with id<" & id & ">"
end subpublic function mProxy_OnMsg(...)
log "OnMsg begine with id<" & id & ">"
RaiseEvent OnMsg(TaskID, Info.InfoType)
log "OnMsg end with id<" & id & ">"
end function
我就接受到了大量的
(process A) DoRaiseEvent begine with...
(Process B) OnMsg begine with ...
(Process B) OnMsg end with 
这样的log,可是DoRaiseEvent end with id这样的log总是要等很久(3秒-2分钟不等),这个时候cpu占用是100,可我的process却只是0(拿不到cpu处理权???为什么老是我的process).
我的问题是:
1.我怀疑是com的事件处理机制的问题.不知道大家有没有碰到.或者有没有其他的解释.
2.是否有什么办法能够帮助我快点接受到DoRaiseEvent end with...
3.另外,曾经出现彻底被锁死的情况,锁死的位置正是再第三个log和第四个log之间,时间超过2个小时.不知道各位有什么经验.希望大家给出问题所在,或者解决的方法,如果你们遇到同样的情况,也行!
如果有人能够帮我解决的话, 我再给400分,决不食言!!!!!

解决方案 »

  1.   

    感觉有互相响应事件,造成死循环的可能。
    RaiseEvent更像是一种过程调用的特殊形式,在事件处理过程没返回前,不会继续后面内容。
    一般这种群发事件,应采用队列形式后台处理机制,这样就不会引起程序锁死的问题。
      

  2.   

    原文:http://landcad.sdau.edu.cn/language/vb/Vb10.html
    应用程序间的简单通信
    心铃学了这么长时间的VB,发觉VB在制作界面上非常简单、方便,但在软件性能、硬件接口等方面离C语言还是差一些,而且VB是学会容易,精通却很难。心铃认为VB的难点和特点在:鼠标事件编程、数据库编程、对文件的存取操作、应用程序间的通信和ActiveX编程等。由于时间关系,在这个讲座中心铃不能和大家一起来探讨所有这些知识,在本讲中,心铃将讲述这些难点中一个有趣而又有用的知识点――应用程序间的通信。
    在VB中可以通过动态数据交换DDE(Dynamic Data Exchange)和动态链接库DLL(Dynamic Link Liberary)方法,实现VB和其他Windows应用程序之间的相互通信。DDL是我们在其他语言中都能接触到的,而且这方面知识对于初学者来讲比较复杂,所以心铃在本讲中将只介绍DDE。所谓“动态数据交换”是指各个应用程序间进行实时动态地数据交换,一旦提供服务的服务方(Sever)改变了交换数据的内容,则接收数据的客户方(Client)将自动更新交换数据的内容。从上面的简介,实现DDE有一个最基本的条件:客户程序和服务程序都正在执行,而且连接的对象正处在服务程序中。由此可见,DDE的内容实际上是服务方和客户方共享的一块内存区。在VB提供的标准控件中,只有文本框、标签框、图像框及Form具有动态交换DDE的功能。DDE的实现有两种方法:代码编程和设计状态下实现,下面分别予以介绍。一.编程时实现DDE连接 确定服务应用程序对象:Word,Excel或其他。 
    确定连接标题――LinkTopic 
    对于接收端LinkTopic返回或设置发送端应用程序和主题(使用于该应用程序中的基本数据的分组);对于发送端窗体LinkTopic返回或设置在DDE对话中发送端窗体需响应的主题。语法:对象名.LinkTopic [= value]。其中value是一个指定DDE语法元素的字符串表达式,它包括 application、topic和 item;格式:服务程序名|标题。例如:txtNumber.LinkTopic = "Excel|sheet1"或txtNumber.LinkTopic = "excel|c:\tabel.xls!sheet1"LinkItem――返回或设置传给接收端的数据。 
    为了指定完整的数据链,LinkTopic应当与LinkItem属性一起使用。语法:对象名.LinkItem [= string]。string是字符串表达式,指定传给目标控件的数据。程序中只能对用做目标的控件设置LinkItem。当Visual Basic窗体是DDE的会话源时,窗体上任何Label名、PictureBox或TextBox控件,都可以是目标使用的application|topic!item字符串的item参数。例如,下面的语法表示从Microsoft Excel到Visual Basic应用程序的有效引用:=VizBasicApplication|MyForm!TextBox1。在Microsoft Excel公式条的目标单元中输入前面的语法即可引用。另外,仅就接收端控件而言,为了指定链接的item元素必须设置相关的LinkItem属性。一个单元引用,比如R1C1,相当于Microsoft Excel工作表中的一个项。LinkMode――连接模式 
    LinkMode决定VB应用程序和其他应用程序之间的DDE连接方式。前面用LinkTopic设置的数据链接必须依靠LinkMode来激活(一般来说,在设置LinkTopic之后应设置LinkMode。),为了指定想要的链接的类型应将LinkMode属性设置为适当的非零数值。语法:对象名.LinkMode [= number]。number指定连接类型的整数,⑴ 当VB应用程序用做目标(客户)的控件,number设置: 常 数
     设置值
     描 述
     
    vbLinkNone
     0
     (缺省值)无DDE交互功能。
     
    vbLinkAutomatic
     1
     自动-每次链接数据改变,目标控件都要更新
     
    vbLinkManual
     2
     手动-当激活LinkRequest方法时,才更新目标控件。
     
    vbLinkNotify
     3
     通知-链接数据改变时,会产生LinkNotify事件,但是只有在LinkRequest方法激活时才会更新目标控件。
      对于接收端控件,LinkTopic的改变将中断现存的链接并终止DDE对话。对于发送端窗体,LinkTopic的改变将中断使用那个主题的所有接收端链接。由于这些原因,在改变LinkTopic之前总是先把LinkMode属性设置为0。在接收端控件LinkTopic改变之后,必须再将LinkMode设置为1(自动)、2(手动)或3(通知)以便用新的主题建立一个对话。⑵ 对作为DDE会话源(服务)的窗体,number的设置值为: 常数
     设置值
     描述
     
    vbLinkNone
     0
     (缺省值)无-没有DDE交互。如果在设计时为0(无),在运行时不能将其改变为1(源)。
     
    vbLinkSource
     1
     源-允许窗体上的任何Label、PictureBox或TextBox控件为与该窗体建立DDE会话的目标应用程序提供数据。如果存在这种链接,VisualBasic在控件内容改变时会自动提醒目标应用程序。另外,目标应用程序能够向窗体上的Label、PictureBox、TextBox控件存放数据。如果设计时LinkMode为1(源),运行时可以将它改为0(无)也可以再改回来。
      LinkTimeout――连接时间设定 
    连接时间定义为:服务程序和客户程序实现一次动态数据交换的最长允许时间。语法:对象名.LinkTimeout [= number],number是数值表达式,指定等待时间。缺省情况下,VB为LinkTimeout属性设置为50(相当于5秒)。对其它设置可以用十分之一秒为单位进行设定。DDE发送端应用的响应时间是不同的。用该属性调整接收端控件等待源应用响应的时间。在源应用需要太长的时间响应时,用LinkTimeout可以避免产生Visual Basic错误。注意:控件最大等待时间长度为65535个十分之一秒,或大约为1小时49分钟。如果将LinkTimeout设置为-1,则在DDE会话中控件以最大时间长度等待响应。用户可以按ESC键来强制终止等待过程。 图 15下面这个例子简单介绍了DDE的程序代码(见图15)。它完成的功能主要是和Excel应用程序之间建立DDE,并利用Excel的函数来构造了一个对数计算器(VB5实际就有)。当你输入数据,VB将自动计算出它的自然对数值。Private Sub cmdCalculate_Click()txtLogValue.LinkMode = 2 '进行手动连接输出文本框的DDEtxtLogValue.LinkRequest '在DDE对话中请求Excel更新输出文本框内容txtLogValue.LinkMode = 0 '关闭DDEEnd Sub Private Sub cmdCalculate_LostFocus()'将光标移到“输入数据”框txtNumber.SetFocus'离开“计算”按钮后,重置txtNumber.Text = ""txtLogValue.Text = ""End Sub Private Sub cmdEnd_Click()EndEnd SubPrivate Sub Form_Load()Dim TaskID As Double  '使用Shell启动源程序Excel,具体见后;'6-程序窗口以一个图标来显示,而焦点仍在当前活动的的窗口。TaskID = Shell("c:\program files\Microsoft Office\office\excel", 6)txtNumber.LinkTopic = "excel|sheet1" '设置连接主题txtNumber.Text = "=LN(A1)" '设置传输内容txtNumber.LinkItem = "R2c1" '设置传输的地址txtNumber.LinkMode = 2 '进行手动连接'在DDE对话过程中将TextBox控件的内容传送给ExceltxtNumber.LinkPoketxtNumber.LinkMode = 0 '关闭DDEtxtNumber.Text = ""txtLogValue.LinkTopic = "excel|sheet1" '建立输出文本框与Excel的联系txtLogValue.LinkItem = "R2c1" '设置联系地址End Sub注释:Shell函数是执行一个可执行文件,返回一个Variant(Double),如果成功的话,代表这个程序的任务ID,若不成功,则会返回0。语法:Shell(pathname[,windowstyle])pathname:必要参数。Variant(String),要执行的程序名,以及任何必需的参数或命令行变量,可能还包括目录或文件夹,以及驱动器。Windowstyle:可选参数。Variant(Integer),表示在程序运行时窗口的样式。如果 windowstyle 省略,则程序是以具有焦点的最小化窗口来执行的,具体参数请查阅帮助。 Private Sub txtNumber_LostFocus()'DDE传输输入数据给ExceltxtNumber.LinkItem = "R1c1"txtNumber.LinkMode = 2txtNumber.LinkPoketxtNumber.LinkMode = 0End Sub 运行上述程序,可以看到VB自动的调用了Excel程序,并完成了设计的DDE功能。 二.设计时实现DDE连接我们从上面的例子中可以看到DDE的编程具有一定的规格,熟悉后就比较简单了。当然,有更简单的方法在VB与其他应用程序之间建立DDE,这就是在设计时进行建立。步骤如下:启动Excel, 
    定义要建立DDE的块,如R1C1:R4C2; 
    转到VB5,激活某个文本框,如“Text1”; 
    在“编辑”菜单上用“粘贴链接”命令,即建立了一个永久性的与窗体一起保存的数据链接。 
    查看该控件的属性,发现VB已经自动地设置了LinkMode、LinkTopic和LinkItem属性:LinkMode-1,Automatic;LinkTopic-Excel|[Book1]Sheet1;LinkItem-R1C1:R4C2。这时,你在Excel中选定的文本框中改变内容,再切换到VB中会发现Text1框的内容也改变了,当你把Text1的MultiLine属性改为True后,则Text1框显示的内容格式就与Excel的相同了。
      

  3.   

    群发时,proxy对象是一个还是多个?
      

  4.   

    我所说的群发,是通过一个proxy调用他的一个方法,多个消息作为参数传入.
    然后对应于每一个消息,proxy会raise一个event回去.请各位高手多多给出意见
      

  5.   

    你可以尝试使用DoEvents来强制运行事件
      

  6.   

    doevents我尝试过了,没有作用,关键还是抢不到时间片,有没有别的方法???
    各位高手过来看看啊!
      

  7.   

    首先,要明确事件与消息的区别
    事件:如果 B 响应 A 的事件,则 A 触发事件后必须等待 B 处理完成事件响应后才能继续执行后继程序;
    消息:如果 B 接收 A 的事件,则 A 发送消息后就可用继续执行后继程序,至于 B 是否接收以及处理了该消息是不用 A 关心的。你要用事件的方式来模拟消息,那么就应该在 B 中实现消息队列处理:
    1、在 B 中需要有地方存放消息队列,可用用一个全局的集合
    Public g_oMessageList As Collection
    2、在响应 A 事件的地方仅添加消息队列,用最快的速度返回
    public function mProxy_OnMsg(...)
      g_oMessageList.Add TaskID & "," & Info.InfoType
    end function
    3、用一个定时器处理消息队列
    Private Sub Timer1_timer()
      dim a() as string
      if g_oMessageList.Count > 0 then
        a = split(g_oMessageList(0))
        g_oMessageList.Remove 1    log "OnMsg begine with id<" & id & ">"
        RaiseEvent OnMsg(a(0), a(1))
        log "OnMsg end with id<" & id & ">"
      end if
    End Sub
      

  8.   

    to Tiger_Zhao(VB老鸟):
        谢谢你的建议,其实我现在的程序已经是这样处理的了.
        现在的问题不是B处理事件的所耗费的时间,其实它非常块就处理完了.关键是这个事件处理结束之后,到A从raise event回复过来,也就是第三和第四条log之间,居然出现被锁死的情况
      

  9.   

    你的意思是 A 中发送消息会有嵌套?那么在 A 中加入标志变量,保证每次只有一个消息发送到 B
    private bInEvent as boolean
    public sub DoRaiseEvent
    while bInEvent
      Doevents
    wend
    binevent = truelog "DoRaiseEvent begine with id<" & id & ">"
    RaiseEvent OnMsg(TaskID, Info.InfoType)
    log "DoRaiseEvent end with id<" & id & ">"binevent = false
    end sub
      

  10.   

    to Tiger_Zhao(VB老鸟):
       good idea!!!我现在就去测试.
      

  11.   

    唉,还是不行.这里如果某一个事件消耗事件过长,其他事件就都会停止在while loop中,造成程序占用了大量cpu事件.
       这两天跟踪程序,发现问题所在是我的某一次调用被hung住了,就是上面的log3和log4之间,造成之后程序无法获取时间片.总觉得是Com的这种进程中事件调用机制有问题.
        神啊!救救我把,我快给这个东西玩疯了!
      

  12.   

    前面,我就说过应采用消息队列处理,与你们后面说的并不一样。可能你并没理解,我再进一步提两点想法吧:1、你的工程可能存在某种逻辑结构问题,造成事件会重复发生,一个消息触发多次事件,这种问题没有全部源码,别人很难帮你;2、我说的队列机制,有点类似于PostMessage方式,SendMessage会锁死,我想与你的情况有点像。而采用Post方式,你完全可避开这种等待,这样最多是处理慢了点,但不会锁死。实现方法:不是在引发事件端,而是要在响应事件端,建立一个消息队列,可用集合对象,事件发生时,只是将消息加入集合对象,就返回,并不在事件过程中处理,这样,就不会造成产生事件端的锁死。处理消息用定时器在后台进行。添加消息时最好采用某种有标识性的关键字,并加上错误监视,这样一个消息只会加入一次,重复消息通过错误处理过滤了。
      

  13.   

    to homezj(小吉):
        多谢,我已经明白了你的意思,其实目前我的程序就是这么设计的.    目前锁死的情况基本已经明确了,是接受事件的程序B在接受事件时已经占用了大量的cpu事件(60%左右, 在其他线程中运行),这时候对B RaiseEvent,B要等待较长事件才能够接受到这个事件,而且在极端的情况下,这个事件会被死锁住. 问题在于无法把事件 raise过去,而不是事件的处理消耗事件太长了.  我打算在B中加入一个timer,过一段事件执行一DoEvents,看看效果如何.
      

  14.   

    抱歉,打错了
    应该是cpu时间
      

  15.   

    哇!这样,就是程序设计的不合理了,看来是程序中用了长循环或长耗时运算却不及时释放CPU引起的了,不能怪任何人,程序设计中一般绝对要注意避免这种现象。另外,用Timer执行DoEvents绝对不是办法,也不会有用!因为,Timer事件就是在系统得到空闲时才会发生,你让它再去DoEvents没有意义,能发生Timer事件的话,你就不会锁死了!正确的方案,还是分解程序B的执行流程,对大工作段要分片执行,或用Timer或用DoEvents,及时释放CPU。你可能要对程序B动大手脚,好的程序应把流畅性放在首位,不能为追求速度而丢失交互机会,那会让程序感觉上反而变慢。
      

  16.   

    主要占用cpu时间的是在另外一个线程的方法,不应该影响到我这个线程的处理.现在的现象基本已经确认了,在raiseevent之后,对方也正确处理了,可是这个结果一直不能回来,造成我的方法被锁死.时间有限,且这应该是com事件处理的问题,所以只好绕过这个问题.请问有什么方法能够检测到程序A已经被锁住,这样我就可以通过程序让A重新启动一次.
    我目前的做法是作一个单独的process,隔100毫秒,调用一次A中的方法,这个方法只作Doevents.这样作能够暂时解决问题,但是肯定还有问题,希望各位老大出点注意.谢谢!!
      

  17.   

    谢谢老鸟和小吉的帮助,请到下面两个帖子接分,每人100.
    http://community.csdn.net/Expert/topic/3759/3759008.xml?temp=.9623377
    http://community.csdn.net/Expert/topic/3759/3759009.xml?temp=.5340235我的问题已经到一阶段,我觉得应该不可能完美解决了,只好选择绕开这个关卡.希望高手们能够多出一点主意.如果能够帮我绕过这个问题,保证400分送上.多谢!
      

  18.   

    Begin2008(重头再来)又见面啦!好呀!To 楼主
    绕开它对你也许是件好事,我也觉得这种用事件方式,引发群起响应(目的却是群发消息)不太合适。
    事件与消息还是有很大区别的,前者是发了必须等回应,后者一般却是发了不用管。我前面说的Post方式,实际就是用事件机制模似消息机制。另外,前面提过,我怀疑过你的事件有重复响应问题,不知你查了没有:
    A除主动产生OnMsg事件外,在mProxy_OnMsg事件过程中也被动产生OnMsg事件,这有点象邮件的自动回复,两头都自动回复,就会出现无限回荡,会锁死至出错为止!
    我想,是否应在OnMsg的参数中加上一个触发端的标识参数,用于OnMsg时进行判断,使自己不至于响应自己触发的事件。对于接分贴,谢谢了!
      

  19.   

    1.同意homezj(小吉)关于重复响应的怀疑。2.我觉得在这个应用中,不适合用 RaiseEvent 的方式进行处理,应该用直接的对象调用进行发送:
    B 中必须有一个类 MsgReceiver,只有一个方法 PostMsg(...),该方法仅负责把收到的消息加入 B 的待处理消息队列中;
    A 做为一个代理,维护一个 MsgReceiver 的集合,所有 B 的实例必须把自己的 MsgReceiver 对象注册到 A 中;A 群发消息就通过循环 MsgReceiver 集合执行 PostMsg() 方法,这样可以避免出现没有响应的情况;
    如果消息最初是由某个 B 的实例产生,想群发到其它的 B 实例中,那么 A 中也应该有一个 MsgReceiver 对象以及待群发消息队列;群发时还要根据消息来源跳过某个 B 的实例。
      

  20.   

    谢谢楼上的建议,我觉得使用postmessage其实是最好用的方式,但是因为B程序是vb做的接口,如果再加入一个timer来peekmessage,可能改动比较大.我目前正在尝试使用对象调用的方法.唉,我最熟悉的是vc和c#,玩起vb还真是有些别扭.呵呵
      

  21.   

    晕倒,改成方法调用,结果居然是抛出异常,原因是我在一个事件中进行跨进程访问,这个时候第二个事件过来了,对同一个进程再次进行跨进程调用.这种行为在COM中是禁止的.我猜测正式这样的原因,造成COM中的某种死锁,才把程序锁死了,唉,真是失败!看来是没有办法解决了.算了,放弃用vb进行进程间访问的机制,改为调用一个vc的lib来作.这个问题就到这里吧!谢谢各位的帮助.