ADODataSet.Open绕过带有`ArgumentOutOfRange`异常的尝试捕获,挂起应用程序-Delphi 10.2

问题描述 投票:2回答:1

我维护在服务器环境中作为服务运行的应用程序。它是多线程的,其中每个线程都根据任务队列工作。该任务队列只是一个以“作业类型”作为其值的字符串列表。因此,尽管可能正在运行多个线程,但是每个线程将是一个不同的作业,并且每个线程一次只在内部运行一个任务。

我遇到在Open上调用TADODataSet时出现的间歇性问题。有时(并非总是如此)并且没有可识别的模式,Data.Win.ADODB会抛出EArgumentOutOfRangeException,绕过我自己尝试捕获任何异常的尝试。此异常将整个线程挂起,并在我完全重新启动该服务之前,阻止了将来的执行。

相对于Delphi的世界来说,我是一个相对较新的人,在这个问题上我已经摸索了很长时间,并且一直在努力寻找任何答案。我的问题是:为什么会发生这种情况,如何停止,捕捉或修复它?

这里是我的违规代码的摘要。这是堆栈跟踪的来源方法。它是从同一单元中的另一个方法调用的,在该方法中,我打开一个不同的数据集,遍历其记录,并在每个记录上调用此函数以基于传入的值获取一些信息。

function TFQFoo.DoSomething(IncNo : Int64): string;
var
  ItemList : string;
  MySQL: string;
  ComponentString: string;
begin
  result:='';
  if IncNo<=0 then
    Exit;
  ItemList := '';
  MyQuery.Close;
  MySQL := 'select ID from tbl ' +
    ' where val = ' + IntToStr(IncNo) +
    ' order by col1 DESC, col2, col3';

  try
    try
      MyQuery.CommandText := (MySQL);
      MyQuery.Open;

      while not (MyQuery.EOF) do
      begin
        if (ItemList <> '') then
          ItemList := ItemList + ',';
        ItemList := ItemList +
          MyQuery.FieldbyName('ID').asstring;
        MyQuery.Next;
      end;
    except
      // exception handling code omitted for brevity -- none of it
      // is ever reached, anyway (see below)
    end;
  finally
    MyQuery.Close;
  end;
  Result := ItemList;
end;

来自异常的调用堆栈表明它正在Open处发生。没有try..catch块会捕获异常并将其记录下来-我必须使用EurekaLog才能查看任何详细信息。堆栈跟踪方法(太大而无法在此处发布)如下所示:

  1. TFQFoo.DoSomething
  2. TDataSet.Open
  3. ...内部事物
  4. TADOConnection.ExecuteComplete
  5. CheckForAsyncExecute
  6. ...
  7. TCustomConnection.GetDataSet
  8. TListHelper.GetItemRange

[考虑到我的TADODataSet组件可能在某种程度上被破坏了/它的属性在运行时发生了改变,我添加了一些日志记录来为我捕获数据,这样我就可以查看其中是否发生了一些时髦的事情。我什么都没看到,但在这里是万一有关系的。

object MyQuery: TADODataSet
  AutoCalcFields = False
  CacheSize = 15
  Connection = FGlobals.RIMSDB
  CursorType = ctStatic
  LockType = ltReadOnly
  CommandText = 
    'select ID from tbl  where val = 202005070074 order by col1 ' +
    'DESC, col2, col3'
  ParamCheck = False
  Parameters = <>
  Left = 32
  Top = 216
end

出于好奇,这实际上是从Data.Win.ADODB中引发异常的方法。请注意例外,我猜测它会跳过我自己的try..catch块并将异常直接发送到EurekaLog。

procedure CheckForAsyncExecute;
var
  I: Integer;
begin
  try
    if not Assigned(pError) and Assigned(pRecordset) and
       ((pRecordset.State and adStateOpen) <> 0) then
      for I := 0 to DataSetCount - 1 do
        if (DataSets[I].Recordset = pRecordset) and (eoAsyncExecute in DataSets[I].ExecuteOptions) then
        begin
          DataSets[I].OpenCursorComplete;
          Break;
        end;
  except
    ApplicationHandleException(Self);
  end;
end;

我尝试过的:

  • 许多次调整ADODataSet本身的组件属性的迭代
  • 从设计器内部使用CommandText和Parameters,并在运行时执行之前分配参数
  • 向查询本身添加/删除(NOLOCK)提示
  • 将问题提交我团队的高级成员以供参考
  • 谷歌搜索(持续数小时)
  • 阅读Delphi和ADO文档(对此没有帮助)
  • 尝试复制-在使用的任何测试系统上,我都无法做到这一点。这使我认为这可能与环境有关,但是我完全不知道如何

如何阻止这种情况发生?我究竟做错了什么?我知道有时候查询执行不会返回结果,但是由于较低级的代码绕过了我自己的catch语句,所以从未看到或遇到过典型的CommandText does not return a result set消息。除此之外,我不知道还要寻找什么。

到目前为止,我只发现了另一起类似的事情,但这仅与未捕获到的异常有关:http://www.delphigroups.info/2/d9/410191.html

delphi ado delphi-10.2-tokyo
1个回答
0
投票

ApplicationHandleException(Self)中对CheckForAsyncExecute()的调用吞噬了异常,这就是为什么未触发except块的原因:

// in Data.Win.ADODB.pas:

procedure CheckForAsyncExecute;
var
  I: Integer;
begin
  try
    ...
  except
    ApplicationHandleException(Self); // <-- a caught exception is NOT re-raised here!
  end;
end;

Data.Win.ADODB单元内部,捕获的异常将调用该单元自己的ApplicationHandleException()函数,如果分配了该函数,则该函数将调用System.Classes.ApplicationHandleException,否则将直接退出:

// in Data.Win.ADODB.pas:

procedure ApplicationHandleException(Sender: TObject);
begin
  if Assigned(System.Classes.ApplicationHandleException) then
    System.Classes.ApplicationHandleException(Sender);
end;

[System.Classes.ApplicationHandleException初始化为nil

在VCL和FMX应用程序中,TApplication构造函数均将TApplication.HandleException()方法分配给System.Classes.ApplicationHandleException,其中HandleException()忽略EAbort异常,并调用TApplication.OnException事件处理程序(如果已分配), TApplication.ShowException()方法或System.SyUtils.ShowException()函数,取决于要处理的异常类型。

[TApplication.ShowException()在弹出的MessageBox中向用户显示该异常的详细信息,然后退出,而System.SysUtils.ShowException()在控制台或MessageBox中向用户显示该异常的详细信息,然后退出。

因此,ADO的CheckForAsyncExecute()绝不会将捕获的异常重新引发到用户代码中。不用说,在服务中显示弹出MessageBox并不是一个好主意,因为用户可能看不到它,因此可以将其关闭。

当然,最好的选择是避免首先提高EArgumentOutOfRangeException。但是,还有其他一些条件也可能引发异常。

因此,您自己处理吞咽的ADO异常并避免弹出MessageBoxes的唯一选择是(直接或通过TApplication.OnException组件分配TApplicationEvents事件处理程序。

© www.soinside.com 2019 - 2024. All rights reserved.