调用使用Delphi XE5编译的TThread.Synchronize时,System.TMonitor.GetFieldAddress中的访问冲突

时间:2015-04-21 23:13:47

标签: delphi delphi-xe5

调用TThread.Synchronize时,System.TMonitor.GetFieldAddress中的间歇性访问冲突,使用Delphi XE5编译

精确时间源(硬件)应该每秒更新一次GUI及其状态。非VCL线程管理它,然后调用Synchronize来更新GUI。在10000-100000次每秒中断后,VCL线程有一个访问冲突,实际上没有调用该时间同步的函数,并且TThread.Synchronize永远不会返回。

非VCL-Thread堆栈和代码段:

:77a47074 ntdll.KiFastSystemCallRet
:77a46a14 ntdll.ZwWaitForSingleObject + 0xc
:751fc3d3 kernel32.WaitForSingleObjectEx + 0x43
:751fc382 kernel32.WaitForSingleObject + 0x12
System.SysUtils.WaitForSyncWaitObj(???,???)
System.SysUtils.WaitOrSignalObj(???,$FFFFFFFF,???)
System.TMonitor.Wait($4843710,4294967295)
System.TMonitor.Wait($4853490,$2E30D40,4294967295)
System.Classes.TThread.Synchronize($898B0A0,False)
System.Classes.TThread.Synchronize((TimeChannel.TTimeChannel.UpdateReaderStatus,$A81CB40))
...
      if ( not Terminated ) and ( not Paused ) and assigned( self ) and assigned( fParent ) then
      begin
        Inc( fParent.fUpdateReaderStatusSyncronizeCounter ); // Counter to see if fParent.UpdateReaderStatus is actually called when Synchronize is called.
        Synchronize( fParent.UpdateReaderStatus ); // Called about once per second.  Runs for around a day before problem.  Thread never returns from here after problem happens.
...
TimeChannel.tRdrStatusControl.Execute
System.Classes.ThreadProc($898B080)
System.ThreadWrapper($8975DA0)
:751fee1c kernel32.BaseThreadInitThunk + 0x12
:77a6399b ntdll.RtlInitializeExceptionChain + 0xef
:77a6396e ntdll.RtlInitializeExceptionChain + 0xc2

VCL Thread stack和code snippets:

...
PROCEDURE tTimeChannel.UpdateReaderStatus;
BEGIN
  try
    Inc( fUpdateReaderStatusCounter ); // counter remains 1 behind the other counter when problem occurs, so UpdateReaderStatus never got called that time and the other thread never returned from Synchronize.

...

class function TMonitor.GetFieldAddress(const AObject: TObject): PPMonitor;
begin
  Result := PPMonitor(PByte(AObject) + AObject.InstanceSize - hfFieldSize + hfMonitorOffset); // Access Violation, read of address 0
end;

...

System.TMonitor.GetFieldAddress(nil)
System.TMonitor.GetMonitor(???)
System.TMonitor.Pulse(nil)

...

function CheckSynchronize(Timeout: Integer = 0): Boolean;
var
  SyncProc: PSyncProc;
  LocalSyncList: TList;
begin
{$IF Defined(MSWINDOWS)}
  if SyncEvent = 0 then
    Exit(False);
{$ELSEIF Defined(POSIX)}
  if (SyncEvent.ReadDes = 0) or (SyncEvent.WriteDes = 0) then
    Exit(False);
{$ENDIF POSIX}
  if TThread.CurrentThread.ThreadID <> MainThreadID then
    raise EThread.CreateResFmt(@SCheckSynchronizeError, [TThread.CurrentThread.ThreadID]);
  if Timeout > 0 then
    WaitForSyncEvent(Timeout)
  else
    ResetSyncEvent;
  LocalSyncList := nil;
  TMonitor.Enter(ThreadLock);
  try
    Pointer(LocalSyncList) := AtomicExchange(Pointer(SyncList), Pointer(LocalSyncList));
    try
      Result := (LocalSyncList <> nil) and (LocalSyncList.Count > 0);
      if Result then
      begin
        while LocalSyncList.Count > 0 do
        begin
          SyncProc := LocalSyncList[0];
          LocalSyncList.Delete(0);
          TMonitor.Exit(ThreadLock);
          try
            try
              if Assigned(SyncProc.SyncRec.FMethod) then
                SyncProc.SyncRec.FMethod()
              else if Assigned(SyncProc.SyncRec.FProcedure) then
                SyncProc.SyncRec.FProcedure();
            except
              if not SyncProc.Queued then
                SyncProc.SyncRec.FSynchronizeException := AcquireExceptionObject
              else
                raise;
            end;
          finally
            TMonitor.Enter(ThreadLock);
          end;
          if not SyncProc.Queued then
            TMonitor.Pulse(SyncProc.Signal) // SyncProc.Signal is nil <----------------
          else
          begin
            Dispose(SyncProc.SyncRec);
            Dispose(SyncProc);
          end;
        end;
      end;
    finally
      LocalSyncList.Free;
    end;
  finally
    TMonitor.Exit(ThreadLock);
  end;
end;

...

System.Classes.CheckSynchronize(???)
Vcl.Forms.TApplication.WndProc((0, 0, 0, 0, 0, 0, (), 0, 0, (), 0, 0, ()))
System.Classes.StdWndProc(590087812,0,0,0)
:752ac4e7 ; C:\Windows\system32\USER32.dll
:752ac5e7 ; C:\Windows\system32\USER32.dll
:752acc19 ; C:\Windows\system32\USER32.dll
:752acc70 USER32.DispatchMessageW + 0xf
Vcl.Forms.TApplication.ProcessMessage(???)
:006202fc TApplication.ProcessMessage + $F8

它有99.99%的时间是有效的,所以出了什么问题,我怎样才能让它继续工作,最后0.01%的时间最终冻结线程?

谢谢, 迪伦

0 个答案:

没有答案