我有一些非常旧的代码(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];
不要退缩:它做错了什么,我应该寻找什么?
最佳答案
这是预期行为 - 打开或关闭 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 端口在后台线程上进行管理,打开或关闭端口需要启动或发出信号停止这些线程。在等待他们释放消息队列的组件服务时,以防事情需要一些时间来同步(例如):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 端口的观点是有效的……我们需要看到更广泛的情况,确切地说,问题是你有什么。
关于Delphi 7、Windows 7、事件处理程序、重入代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18095302/