Clientdataset Append / Post间歇性失败

时间:2016-03-23 16:42:04

标签: delphi tclientdataset

我每秒钟都有一条外部信息 消息有效负载保存在ClientDataSet中并显示在dbGrid中 不涉及数据库。仅限RAM存储。
这很好,

当数据集为空并且第一次填充时,我有间歇性问题。

代码如下:

procedure TCtlCfg_DM_WarningsFaults_frm.DecodeRxFrame(Protocol: TProtocolSelection;
//          PROVA UTAN VAR                            VAR Frame : CAN_Driver.TCAN_Frame);
                                                      Frame : CAN_Driver.TCAN_Frame);
var
  OldRecNo          : integer;
//  OldIxname         : string;
//  bMark       : TBookMark;
  WasFiltered       : Boolean;
  IdBitFields       : TCanId_IdBitFields;
  Msg               : TCan_Msg;
  MsgType           : integer;
  GlobalNode        : TCanId_GlobalNode;
  LocalNode         : TCanId_LocalNode;
  SubNode           : TCanId_SubNode;
  EntryType         : integer;
  SubSystemType     : integer;
  SubSystemDeviceId : integer;
  IsActive          : Boolean;
  IsAcked           : Boolean;

begin
  with cdsWarningsFaults do
    begin
      if not Active then Exit;

      Msg := Frame.Msg;
      IdBitFields := DecodeCanId(Protocol, Frame.ID);
      if IdBitFields.SubNode     <> cSubNode_Self      then Exit; // Ignore non controller/slave messages
      if IdBitFields.AddressMode <> cCanId_AddrMode_CA then Exit;

      MsgType := IdBitFields.MessageType;
      if MsgType <> cMsg_CTL_CA_Broadcast_WarningAndFaultList then Exit;
      if Frame.MsgLength < 5 then Exit;

      GlobalNode  := IdBitFields.GlobalNode;
      LocalNode   := IdBitFields.LocalNode;
      SubNode     := IdBitFields.SubNode;

      // Silent exit if wrong node
      if GlobalNode <> fNodeFilter.GlobalNode then Exit;
      if LocalNode  <> fNodeFilter.LocalNode  then Exit;
      if SubNode    <> fNodeFilter.SubNode    then Exit;

      EntryType         := Msg[1];
      SubSystemType     := Msg[2];
      IsActive          := (Msg[3] = 1);
      SubSystemDeviceId := Msg[4];
      IsAcked           := (Msg[8] = 1);

      DisableControls; // 2007-12-03/AJ Flytta inte scrollbars under uppdatering
      OldRecNo    := RecNo;
  //    OldIxName   := IndexName;  // Save current index
  //    IndexName   := IndexDefs.Items[0].Name;
      WasFiltered := Filtered;  // Save filter status
      Filtered    := False;

      try
        try
          if Findkey([GlobalNode, LocalNode, SubNode, EntryType, SubSystemType, SubSystemDeviceId]) then
            begin // Update record
              Edit;
              FieldByName('fIsActive').AsBoolean  := IsActive;
              FieldByName('fIsAcked').AsBoolean   := IsAcked;
              FieldByName('fTimeout').AsDateTime  := GetDatabaseTimeoutAt;
              Post;
              MainForm.AddToActivityLog('CtlCfg_DM_WF: DecodeRxFrame: Efter Edit.    N=' + IntToStr(GlobalNode) + ' ' +
                                                                                           IntToStr(LocalNode)  + ' ' +
                                                                                           IntToStr(SubNode)    +
                                        '  RecCnt=' + IntToStr(RecordCount) +  ' ET=' + IntToStr(EntryType) + ' SST=' + IntToStr(subSystemType) + ' SSD=' + IntToStr(SubSystemDeviceId), False);
            end
          else
            begin // Create new record
              Append;
              MainForm.AddToActivityLog('CtlCfg_DM_WF: DecodeRxFrame: Efter Append.  N=' + IntToStr(GlobalNode) + ' ' +
                                                                                           IntToStr(LocalNode)  + ' ' +
                                                                                           IntToStr(SubNode)    +
                                        '  RecCnt=' + IntToStr(RecordCount) +  ' ET=' + IntToStr(EntryType) + ' SST=' + IntToStr(subSystemType) + ' SSD=' + IntToStr(SubSystemDeviceId), False);

              try
              FieldByName('fGlobalNode').AsInteger        := GlobalNode;
              FieldByName('fLocalNode').AsInteger         := LocalNode;
              FieldByName('fSubNode').AsInteger           := SubNode;
              FieldByName('fEntryType').AsInteger         := EntryType;
              FieldByName('fSubSystemType').AsInteger     := SubSystemType;
              FieldByName('fSubSystemDeviceId').AsInteger := SubSystemDeviceId;
              FieldByName('fIsActive').AsBoolean          := IsActive;
              FieldByName('fIsAcked').AsBoolean           := IsAcked;
              FieldByName('fTimeout').AsDateTime          := GetDatabaseTimeoutAt;
              finally
                try
                  Post;   // VArför biter inte denna post så att det blir edit nästa gång
                except
                  MainForm.AddToActivityLog('CtlCfg_DM_WF: DecodeRxFrame: Exception efter Post.', True);
                end;
              MainForm.AddToActivityLog('CtlCfg_DM_WF: DecodeRxFrame: Efter Post.    N=' + IntToStr(GlobalNode) + ' ' +
                                                                                           IntToStr(LocalNode)  + ' ' +
                                                                                           IntToStr(SubNode)    +
                                        '  RecCnt=' + IntToStr(RecordCount) +  ' ET=' + IntToStr(EntryType) + ' SST=' + IntToStr(subSystemType) + ' SSD=' + IntToStr(SubSystemDeviceId), False);
              end;
            end;
        except
          on E: Exception do
            begin
              MainForm.AddToActivityLog('Post exception message:      [' + E.Message + ']', False);
              MainForm.AddToActivityLog('Post exception class:        [' + E.ClassName + ']', False);
              MainForm.AddToActivityLog('Post exception Error code:   [' + IntToStr(EDBCLIENT (E).ErrorCode) + ']', False);
              MainForm.AddToActivityLog('Post exception ReadOnly is:  [' + BoolToStr(ReadOnly) + ']', False);
              MainForm.AddToActivityLog('Post exception CanModify is: [' + BoolToStr(CanModify) + ']', False);
              MainForm.AddToActivityLog('DecodeRxFrame: Exception inside FindKey block', False);
              Cancel;
            end;
        end;

      finally
   //     IndexName := OldIxName;   // Restore previous index
        Filtered  := WasFiltered; // Restore filter state
        if (OldRecNo >= 1) and (OldRecNo <= RecordCount) then RecNo := OldRecNo;
        EnableControls;
      end;
    end;

  //MainForm.AddToActivityLog('DecodeRxFrame: Exit ur proceduren', False);
end;

问题是当记录尚不存在时, 我需要添加一条新记录 它通常工作正常,但很多时候似乎POST不起作用,
当新数据出现时,附加内容会重复几次或多次。

突然,附加工作正常,后续更新使用编辑完成 而据我所知,之后它会永远有效。

问题是间歇性的,成功所需的尝试次数各不相同 这感觉像是一个时间问题,但我无法弄清楚。

任何想法都非常感激。
谢谢,
Anders J

1 个答案:

答案 0 :(得分:3)

正如我的评论中所提到的,可以通过使用日志提取来了解代码如何流动。 (另外作为附注,有时您需要注意日志记录系统的可靠性。您的日志记录至少部分是家酿,所以当您随意通过True /我不知道它意味着什么False方法的AddToActivityLog值。)

但是,我仍然可以提供一些指导来帮助您确定问题。我还有一些一般性意见可以帮助您改进代码。

使用日志来缩小问题范围你并不害羞:这是件好事! 但是你的技术可以使用一点改进。您正试图确定Post方法周围出了什么问题。鉴于这样一个明确的目标,您的日志记录似乎令人惊讶地随意。

您应该执行以下操作:

//Log the line before Post is called. It confirms when it is called.
//Log important state information to check you're ready to post "correctly"
//In this it's especially useful to know the Sate of the dataset (dsEdit/dsInsert).
Post;
//Log the line after Post to confirm it completed.
//Note that "completed" and "succeeded" aren't always the same, so...
//Again, you want to log important state information (in this case dsBrowse).

如果你有这个记录,你可以(例如)告诉我们:

  • 在调用Post数据集之前,dsInsert状态。
  • 并且(假设没有例外):在调用Post后,数据集仍处于dsInsert状态。
  • 注意:如果它在dsBrowse中但Post仍被视为“不成功”,则会被告知您在发布之前和之后记录该记录的详细信息。

所以现在:发布“完成”没有记录被“发布”将提供更多的东西:

  • 哪些事件被挂钩到数据集?特别要考虑用于验证的事件。
  • 由于您正在使用TClientDataSet,因此您需要查看OnPostError这一重要事件。 DBClient使用此回调机制在发布时通知客户端错误。

如果您记录OnPostError,我相信您会更好地了解问题。

最后我提到你的代码还有很多其他问题;特别是错误处理。

  • 在您知道如何正确使用之前,请勿使用 。当你知道如何正确使用它时,你也会知道使用它是没有充分理由的。就目前而言,你的代码实际上是2个字符,而不是一个微妙的bug,甚至可能是一个噩梦甚至意识到它甚至存在;但是完整的非问题 没有 。 (你声明并使用了一个名为IsActive的属性,与TDataSet的Active只有两个字符不同。我相信你没有意识到这一点;他们的区别仅仅是一个意外。但是,如果他们有同样,会非常悄悄地使用错误的。)

  • 你需要编写更小的方法 - 更小!像你这样的代码是调试的噩梦,并且非常善于隐藏错误。

  • 您的异常处理从根本上说是错误的:

您对日志记录和异常处理的评论表明,您只是简单地添加了绝望的内容。我认为理解是什么让你的日志记录变得有用并避免混乱是值得的。让我们仔细看看问题最多的部分。

/_   try
/_     FieldByName('fGlobalNode').AsInteger        := GlobalNode;
/_E    FieldByName('fLocalNode').AsInteger         := LocalNode;
  |    FieldByName('fSubNode').AsInteger           := SubNode;
  |    FieldByName('fEntryType').AsInteger         := EntryType;
  |    FieldByName('fSubSystemType').AsInteger     := SubSystemType;
  |    FieldByName('fSubSystemDeviceId').AsInteger := SubSystemDeviceId;
  |    FieldByName('fIsActive').AsBoolean          := IsActive;
  |    FieldByName('fIsAcked').AsBoolean           := IsAcked;
  |    FieldByName('fTimeout').AsDateTime          := GetDatabaseTimeoutAt;
  |_ finally
  /_   try
  /_     Post;
  /    except
  |      MainForm.AddToActivityLog(..., True);
  |    end;
  |_   MainForm.AddToActivityLog(..., False);
  /  end;
  |
 ...

所以,在上面的代码中:

  • 如果没有例外,你只需从一行走到下一行。
  • 但是一旦发生异常,你就会跳到下一个finally / except块。
  • 第一个问题是:如果您尚未设置字段值,为什么要尝试强制Post。当你最终得到的数据只有他们应该只有一小部分的记录时,这会令你感到头痛 - 除非你 幸运 并且Post因为关键而失败数据丢失。
  • 当在异常期间最终完成时,代码会立即跳转到最后一个/除了在调用堆栈中。
  • 除了略有不同之外,只有当 出错时才会调用它。 (最后保证它将被调用/没有例外。

提示:(适用于99%的异常处理案例。

  • 在成功和错误的情况下,只使用最后一次清理 必须发生
  • 嵌套你的try finally块:模式是<Get Resource>try .... finally<Cleanup>end(唯一可以在....内进行另一项资源保护的地方。)
  • 在大多数情况下,请避免使用
  • 上一条规则的主要例外是仅在出现错误时才需要清理。在这种情况下:执行清理并重新引发异常。
  • 除此之外:如果 完全解决 错误情况,则只执行块而不重新加注。 (意思是异常吞咽者 之后的代码行真正不关心先前的异常