我有一些非常旧的代码(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也没有消息处理部分但是
当从单击事件调用此代码时,它会完成一部分,然后被绘制事件打断。
我所做的唯一跟踪是输出调试。我可以看到第一个字符串在入口上重复,然后第二个字符串在出口上显示。这是真的,还是幻觉?
轨迹如下:
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。没有线程代码。是的,还有更多的代码,不,它在"端口集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端口的观点是有效的。。。我们需要看到更广阔的前景,以及问题到底是什么。
标准绘制事件不能单独发生,只能由消息检索触发。因此,如果Serial组件本身或您分配给它的事件处理程序正在执行某些操作,为新消息抽取调用线程的消息队列,那么您显示的代码可能会以您描述的方式中断。
由于您在事件处理程序开始时关闭端口,如果有机会触发事件两次(即通过在代码中的任何位置调用Application.ProcessMessages
,或直接从工作线程调用TMainform.portset()
),新实例将关闭您的端口,而旧实例尝试通过它进行通信,这将导致错误。AFAIS有两种解决方案:
-
更快但最不可忍受的是用Mutex(或不是同步对象但可以用作同步对象的事件)保护您的整个功能,但这只会隐藏您所犯的编码错误。
-
更专业的解决方案是找到引发竞争条件的地方,然后修复您的代码。您可以通过搜索对
Application.ProcessMessages()
和TMainform.portset()
的所有引用来做到这一点,并确保它们不会被称为paranelly。如果在提到的任何一个函数上都找不到引用,那么问题仍然可能是由运行代码的多个实例引起的(因为它不会创建多个com端口:)。
Remy Lebeau因为回答了这个问题而受到赞扬,因为正如我所要求的,这是对一般问题的一般回答。但如果没有他对乌韦·拉贝的回应,这是不够的。
最终证明雷米·勒博是正确的是J的特殊回答,指出了代码失败的具体点。
还要感谢David Heffernan询问"为什么响应WM_PAINT的代码调用端口集",这也提出了一个一般性的观点。是的,快速解决方案只是阻止从绘制事件处理程序到comms代码的路径,但我没有意识到更一般的一点。
我将查看comms代码,看看是否还有更多这样的问题,我将查看事件处理程序,看看是否有更多这样的错误,所以感谢所有阅读并考虑过这个问题的人。