印地 gettickdiff64() 18446744073709551600 问题

indy gettickdiff64() 18446744073709551600 problem

我发现 gettickdiff64 函数有时会导致 18446744073709551600(或 18446744073709551601)并导致 编程到 运行 不正确。 通常没有大于 300000

的结果

这可能是关于什么的? 我是否应该始终针对此问题进行额外检查?

32位VCL应用程序。 我使用 Delphi 10.4.1(其独立版本 10.6.2.0) 运行 on: 64 bit Windows Server 2012 R2 Foundation / intel xeon cpu E3-1225 v5 3.3 Ghz.

代码结构如下:

TMyClass = class
 private
  //.............
  lastSetTime: uint64;
  critic: TCriticalSection;
 public
  //.............
  procedure setLastSetTime( ltime: uint64 );
  function getLastSetTime: uint64;
end;

procedure TMyClass.setLastSetTime( ltime: uint64 );
 begin
  critic.enter;
  try
    lastSetTime := ltime;
  finally
   critic.leave;
  end;
 end;

function TMyClass.getLastSetTime: uint64;
 begin
  critic.enter;
  try
    result := lastSetTime;
  finally
   critic.leave;
  end;
 end;


...........


procedure controlAll(); //------>this is called from within thread every 5 minutes
 var oki: boolean;
     starttime, tdiff, ltime: uint64;
     i: integer;
     myC, sC: TMyClass;
 begin
   oki := false;
   starttime := ticks64();
   while ( oki = false ) and ( gettickdiff64( starttime, ticks64 ) < 40000 ) do
   begin
     //.........
     //.........
     sC := nil;
     with myClassList.LockList do
     try
       if count > 0 then //---> has about 50000
       begin
         i := 0;
         while i < count do
         begin
           myC := TMyClass( items[ i ] );
           ltime := myC.getLastSetTime();
           tdiff := gettickdiff64( ltime, ticks64() );
           if tdiff > 50000 then
           begin
             logToFile( tdiff.ToString + ' ' + ltime.ToString );  //-----> every 5 minutes 50-60 log lines occur like this: 18446744073709551600 468528329
             //..........
             //.........
             sC := myC;
             delete( i );
             break;
           end; 
           inc( i );
         end;
       end;
     finally
       myClassList.UnlockList;
     end; 

     if sC = nil then oki := true
     else
     begin
       //..........
       //..........
     end;
   end;
 end;

设置该值的代码结构如下

classListArray 将所有 类 类型的 TMyClass 按服务器和频道编号分组。 myClassList 将所有 类 类型的 TMyClass 一个接一个地附加,而不分组。 classListArray 用于花费更少 CPU 和处理速度更快。 这两个列表在访问 类 时没有相互保护。 只有在增删改写类.

时才会互相保护
classListArray: array[ 1..250, 1..12 ] of TThreadList;

//.................

procedure ServerExecute(AContext: TIdContext);
 var Ath: TMypeer;
     severNum, channelNum, clientNum, i, j, num: integer;
     pSize: word;
     stream: Tmemorystream;
     packageNum: byte;
 begin
   try
      Ath := TMypeer( AContext );

      serverNum  := Ath.getServerNum();
      channelNum := Ath.getChannelNum();

      Ath.SendQueue();

      if AContext.Connection.IOHandler.InputBufferIsEmpty then
        if not AContext.Connection.IOHandler.CheckForDataOnSource( 50 ) then Exit;
   
      clientNum := AContext.Connection.IOHandler.ReadInt32( false );
      pSize := AContext.Connection.IOHandler.ReadUInt16( false );

      stream := TMemorystream.create;
      try
        AContext.Connection.IOHandler.ReadStream( stream, pSize );

        stream.Seek( 0, soFromBeginning );
        if clientNum <> 0 then
        begin
          //...........
        end
        else
        begin 
          stream.ReadBuffer( packageNum, sizeof( packageNum ) );
          if packageNum = 10 then
          begin
            stream.ReadBuffer( num, sizeof( num ) );
            for i := 1 to num do
            begin
               stream.ReadBuffer( clientNum, sizeof( clientNum ) );
               with classListArray[ serverNum, channelNum ].LockList do
               try
                 if count > 0 then
                  for j := 0 to count - 1 do
                   begin
                     if TMyClass( items[ j ] ).getClientNum = clientNum then 
                     begin
                        TMyClass( items[ j ] ).setLastSetTime( ticks64 ); //**********
                        break;
                     end; 
                   end;
               finally
                 classListArray[ serverNum, channelNum ].unLockList;
               end;
            end;
          end
          else
          //.........
        end;
      finally
        stream.free;
      end;
   except on e:exception do
      begin
        if E is Eidexception then raise
        else
        begin
           logToFile( e.message );
           //..........
        end;
      end; 
   end;
 end;

根据您的日志,ltime468528329GetTickDiff64(ltime, Ticks64()) return 编辑 18446744073709551600。给定 GetTickDiff64() 的简单实现(其中 TIdTicksUInt64):

function GetTickDiff64(const AOldTickCount, ANewTickCount: TIdTicks): TIdTicks;
{$IFDEF USE_INLINE}inline;{$ENDIF}
begin
  {This is just in case the TickCount rolled back to zero}
  if ANewTickCount >= AOldTickCount then begin
    Result := TIdTicks(ANewTickCount - AOldTickCount);
  end else begin
    Result := TIdTicks(((High(TIdTicks) - AOldTickCount) + ANewTickCount) + 1);
  end;
end;

给定 AOldTickCount=468528329,此代码可以 return 18446744073709551600 的唯一方法是 ANewTickCount18446744074178079929468528313

由于 VCL 仅在 Windows 上运行,并且在 Windows 上运行 Ticks64() 只是 Win32 GetTickCount64() 函数在 Vista 及更高版本上的薄包装,它非常Windows 不太可能为当前的滴答计数器产生像 18446744074178079929 这样的天文数字(即从启动后的 213503982340 天)。所以它必须 returned 468528313 代替,这更合理(从启动后仅 5.4 天)。那比 ltime=46852832916 毫秒,所以 GetTickDiff64() 会假设 Windows' 滴答计数器已经超过 High(UInt64) 并回绕到0(这对于 64 位滴答计数器来说不太可能 在我们的一生中做到。

因此,您需要调试代码并弄清楚 Ticks64()/Windows 如何可能 return 468528329 然后 return 468528313。我怀疑它真的没有这样做,而且您的代码中更有可能存在我们看不到的错误,它正在将错误的值存储到 TMyClass.lastSetTime 开始。

也就是说,您可以考虑摆脱 TCriticalSection 的开销,并使用 TInterlocked 代替 read/write 您的 UInt64 成员原子地。

或者,尝试使用 Delphi 自己的 TStopWatch 而不是手动跟踪报价。