1

我有一些非常旧的代码(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

这是一个竞争条件:在单击事件处理程序代码完成之前调用表单的绘制事件处理程序,这会导致失败。序列号是 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]; 

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

4

4 回答 4

4

这是预期的行为 - 打开或关闭 aTApdComPort将为消息队列提供服务,特别是通过调用它命名的函数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 端口在后台线程上进行管理,打开或关闭端口需要启动或发出信号停止这些线程。在等待他们释放消息队列的组件服务时,以防事情需要一些时间来同步(例如):

if Assigned(ComThread) then
begin
     {Force the comm thread to wake...}
     FSerialEvent.SetEvent;
     {... and wait for it to die}
     ResetEvent(GeneralEvent);
     while (ComThread <> nil) do
     SafeYield;
end;

但是,您还没有真正向我们展示足够多的自己的代码来说明为什么这在您的情况下是有问题的。我认为大卫关于在油漆处理程序中操纵 com 端口的观点是有效的......我们需要看到更广泛的画面,以及确切地说,问题在于你有什么。

于 2013-08-07T13:42:20.273 回答
2

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

于 2013-08-07T05:58:28.183 回答
1

由于您在事件处理程序的开头关闭端口,如果有任何机会触发事件两次(即通过Application.ProcessMessages从代码中的任何位置调用,或TMainform.portset()直接从工作线程调用),新实例将关闭您的端口,而年长者试图通过它进行通信,这将导致错误。AFAIS有两种解决方案:

  • 更快但最难以忍受的方法是使用 Mutex(或不是同步对象但可以用作一个对象的事件)保护您的整个功能,但这只会隐藏您所做的编码错误。

  • 更专业的解决方案是找到引发竞争条件的位置,然后修复您的代码。您可以通过搜索对 and 的所有引用来做到这一点Application.ProcessMessages()TMainform.portset()并确保它们不会被并行调用。如果在上述任何一个函数上都找不到引用,则问题仍然可能是由运行代码的多个实例引起的(因为它不会创建多个 com 端口:))。

于 2013-08-07T07:27:45.957 回答
0

Remy Lebeau 因回答这个问题而受到赞誉,因为正如我所要求的,这是对一般问题的一般答复。但如果没有他对 Uwe Raabe 的回应,这将是不够的。

最终证明 Remy Lebeau 正确的是 J 的特殊答案,指出了代码失败的具体点。

还要感谢 David Heffernan 询问“为什么响应 WM_PAINT 调用端口集的代码”,这也提出了一般性观点。是的,快速修复只是阻止从绘制事件处理程序到通信代码的路径,但我没有认识到更普遍的观点就这样做了。

我会看看通讯代码,看看是否有更多这样的问题,我会看看事件处理程序,看看是否还有更多这样的问题,所以感谢所有阅读和考虑了这个问题。

于 2013-08-08T07:47:29.883 回答