Delphi 7,Windows 7,事件处理程序,返回代码

Delphi 7,Windows 7,事件处理程序,返回代码,delphi,windows-7,event-handling,Delphi,Windows 7,Event Handling,我有一些非常旧的代码(15年以上),过去运行正常,在使用较旧软件版本的较旧较慢的机器上。它现在不太好用,因为如果没有通过比赛条件。这是一个一般性的问题:告诉我为什么我应该知道并预期这段代码中的失败,这样我就可以识别其他代码中的模式: procedure TMainform.portset(iComNumber:word); begin windows.outputdebugstring(pchar('portset ' + inttostr(icomnumber))); with mainfo

我有一些非常旧的代码(15年以上),过去运行正常,在使用较旧软件版本的较旧较慢的机器上。它现在不太好用,因为如果没有通过比赛条件。这是一个一般性的问题:告诉我为什么我应该知道并预期这段代码中的失败,这样我就可以识别其他代码中的模式:

procedure TMainform.portset(iComNumber:word);
begin
windows.outputdebugstring(pchar('portset ' + inttostr(icomnumber)));

with mainform.comport do
try
    if open then open := False; // close port
    comnumber:=iComNumber;
    baud:=baudrate[baudbox.itemindex];
    parity:=pNone;
    databits:=8;
    stopbits:=1;
    open:=true;
    flushinbuffer;
    flushoutbuffer;
    if open then mainform.statusb.Panels[5].text:=st[1,langnum] {Port open}
      else mainform.statusb.Panels[5].text:=st[2,langnum]; {port set OK}
except
  on E: exception do begin
    windows.OutputDebugString('exception in portset');
    mainform.statusb.Panels[5].text:=st[3,langnum];
    beep;
    beep;
  end;
end;
windows.outputdebugstring('portset exit');
end;
请注意,flushinbuffer受EnterCriticalSection()保护;AFAIK没有其他内容受到保护,AFAIK没有消息处理部分但是

当从单击事件调用此代码时,它会部分通过,然后被绘制事件打断

我所做的唯一跟踪是使用outputdebugstring。我可以看到第一个字符串在进入时重复,第二个字符串在退出时显示。这是真的,还是幻觉

跟踪结果如下所示:

4.2595    [4680] graph form click event
4.2602    [4680] portset 1 'from click event handler'
4.2606    [4680] graph form paint event
4.2608    [4680] portset 1 'from paint event handler'
4.2609    [4680] portset exit

4.3373    [4680] portset exit
这是一个竞争条件:在click事件处理程序代码完成之前调用表单的paint事件处理程序,这会导致失败。串行代码是AsyncPro。没有线程代码。是的,还有更多的代码,不,在“portset 1”之前它没有做任何特别的事情,但它在到达之前会写入表单:

with graphform do begin
    if not waitlab.Visible then begin
       waitlab.visible:=true;
       waitprogress.position:=0;
       waitprogress.visible:=true;
       waitprogress.max:=214;
    end;
end;
mainform.Statusb.panels[5].text:=gcap[10,langnum]; 

不要退缩:它做错了什么,我应该寻找什么?

标准的绘制事件不能单独发生,只能通过消息检索触发。因此,您所显示的代码可以按照您所描述的方式中断的唯一方式是,如果串行组件本身或您分配给它的事件处理程序正在执行某些操作,从而将调用线程的消息队列中的新消息泵送出去。

标准绘制事件不能自行发生,它只能由消息检索触发。因此,您所显示的代码可以按照您所描述的方式被中断的唯一方式是,如果串行组件本身或您分配给它的事件处理程序正在执行某些操作,从而为调用线程的消息队列输送新消息。

因为您在事件处理程序的开头关闭了端口,如果有可能触发事件两次(即从代码的任何位置调用
Application.ProcessMessages
,或直接从工作线程调用
TMainform.portset()
),则新实例将关闭您的端口,而旧实例将尝试通过它进行通信,这将导致一个错误。AFAI有两种解决方案:

  • 更快但最难忍受的方法是使用互斥锁(或者不是同步对象但可以作为一个对象使用的事件)来保护整个函数,但这只会隐藏您所犯的编码错误

  • 更为专业的解决方案是找到竞争条件出现的地方,然后修复代码。您可以通过搜索对
    Application.ProcessMessages()
    tmaninform.portset()
    的所有引用来完成此操作,并确保不会并行调用它们。如果在上述两个函数中都找不到引用,则问题仍然可能是由运行多个代码实例引起的(“因为它不会创建多个com端口:))


由于您在事件处理程序的开头关闭了端口,因此如果有可能触发两次事件(即从代码的任意位置调用
Application.ProcessMessages
,或直接从工作线程调用
tmaninform.portset()
),新实例将关闭您的端口,而旧实例尝试通过它进行通信,这将导致错误。AFAI有两种解决方案:

  • 更快但最难忍受的方法是使用互斥锁(或者不是同步对象但可以作为一个对象使用的事件)来保护整个函数,但这只会隐藏您所犯的编码错误

  • 更为专业的解决方案是找到竞争条件出现的地方,然后修复代码。您可以通过搜索对
    Application.ProcessMessages()
    tmaninform.portset()
    的所有引用来完成此操作,并确保不会并行调用它们。如果在上述两个函数中都找不到引用,则问题仍然可能是由运行多个代码实例引起的(“因为它不会创建多个com端口:))


这是预期行为-打开或关闭
TApdComPort
将为消息队列提供服务,特别是通过调用它命名的函数
SafeYield

function SafeYield : LongInt;
    {-Allow other processes a chance to run}
  var
    Msg : TMsg;
  begin
    SafeYield := 0;
    if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then begin
      if Msg.Message = wm_Quit then
        {Re-post quit message so main message loop will terminate}
        PostQuitMessage(Msg.WParam)
      else begin
        TranslateMessage(Msg);
        DispatchMessage(Msg);
      end;
      {Return message so caller can act on message if necessary}
      SafeYield := MAKELONG(Msg.Message, Msg.hwnd);
    end;
  end;
TApdComPort
是一个异步组件-com端口在后台线程上管理,打开或关闭端口需要启动或发出停止这些线程的信号。在等待他们释放组件服务时,消息队列将显示,以防事情需要一些时间才能同步(例如):

如果已分配(ComThread),则
开始
{强制通信线程唤醒…}
FSerialEvent.SetEvent;
{…等待它死去}
重置事件(GeneralEvent);
而(ComThread nil)做什么
安全产量;
终止

然而,您并没有向我们展示足够的代码来说明为什么在您的案例中这是有问题的。我认为David关于com端口在绘制处理程序中被操纵的观点是正确的。。。我们需要了解更广泛的情况,以及您的问题所在。

这是预期的行为-打开或关闭
TApdComPort
将为消息队列提供服务,特别是通过调用它命名的函数
SafeYield

function SafeYield : LongInt;
    {-Allow other processes a chance to run}
  var
    Msg : TMsg;
  begin
    SafeYield := 0;
    if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then begin
      if Msg.Message = wm_Quit then
        {Re-post quit message so main message loop will terminate}
        PostQuitMessage(Msg.WParam)
      else begin
        TranslateMessage(Msg);
        DispatchMessage(Msg);
      end;
      {Return message so caller can act on message if necessary}
      SafeYield := MAKELONG(Msg.Message, Msg.hwnd);
    end;
  end;
TApdComPort
是一个异步组件-com端口在后台线程上管理,打开或关闭端口需要启动或发出停止这些线程的信号。在等待他们释放组件服务时,消息队列将显示,以防事情需要一些时间才能同步(例如):

如果已分配(ComThread),则
开始
{强制执行命令