我维护在服务器环境中作为服务运行的应用程序。它是多线程的,其中每个线程都根据任务队列工作。该任务队列只是一个以“作业类型”作为其值的字符串列表。因此,尽管可能正在运行多个线程,但是每个线程将是一个不同的作业,并且每个线程一次只在内部运行一个任务。
我遇到在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才能查看任何详细信息。堆栈跟踪方法(太大而无法在此处发布)如下所示:
[考虑到我的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;
我尝试过的:
如何阻止这种情况发生?我究竟做错了什么?我知道有时候查询执行不会返回结果,但是由于较低级的代码绕过了我自己的catch语句,所以从未看到或遇到过典型的CommandText does not return a result set
消息。除此之外,我不知道还要寻找什么。
到目前为止,我只发现了另一起类似的事情,但这仅与未捕获到的异常有关:http://www.delphigroups.info/2/d9/410191.html
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
事件处理程序。