【发布时间】:2020-05-08 19:02:26
【问题描述】:
我维护一个在服务器环境中作为服务运行的应用程序。它是多线程的,每个线程都根据任务队列工作。此任务队列只是一个以“作业类型”为值的字符串列表。因此,虽然可能有多个线程正在运行,但每个线程将是不同的作业,并且每个线程在内部一次只运行一个任务。
在TADODataSet 上调用Open 时,我遇到了间歇性问题。有时,并非总是如此,并且没有明显的模式,Data.Win.ADODB 会抛出一个EArgumentOutOfRangeException,绕过我自己捕获任何异常的尝试。此异常会挂起整个线程并阻止将来执行,直到我完全重新启动服务。
作为 Delphi 世界的新手,我已经在这个问题上摸索了很长一段时间,并且一直在努力寻找任何答案。我的问题是:为什么会发生这种情况,我该如何阻止、捕捉或修复它?
这是我的违规代码的 sn-p。这是堆栈跟踪的来源方法。它是从同一单元中的另一个方法调用的,在这里我打开一个不同的数据集,循环遍历其记录,并在每条记录上调用此函数以根据传入的值获取一些信息。
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 才能查看任何详细信息。堆栈跟踪方法(太大,无法在此处发布)如下所示:
- TFQFoo.DoSomething
- TDataSet.Open
- ...内部的东西
- TADOConnection.ExecuteComplete
- CheckForAsyncExecute
- ...
- TCustomConnection.GetDataSet
- 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 和参数,并在运行时执行之前分配参数
- 向查询本身添加/删除 (NOLOCK) 提示
- 将问题提交给我团队的高级成员以征求意见
- 谷歌搜索(数小时)
- 阅读 Delphi 和 ADO 文档(对此没有收获)
- 尝试复制 - 我从来没有在我使用的任何测试系统上发生这种情况。这让我认为它可能与环境有关,但我完全不知道如何
我的问题,重申一下:
如何阻止这种情况发生?我究竟做错了什么?我不想只抓住EArgumentOutOfRangeException;我想首先了解它为什么会发生,并防止它在未来发生。
我知道有时,查询执行不会返回结果,但是由于低级代码绕过了我自己的 catch 语句,因此从未见过或遇到过典型的 CommandText does not return a result set 消息。除此之外,我不知道还要寻找什么。
到目前为止,我只发现了另一个类似的情况,但它仅与未捕获的异常有关:http://www.delphigroups.info/2/d9/410191.html
【问题讨论】:
-
如果您还没有尝试过在
CheckForAsyncExecute中的ApplicationHandleException(Self)上放置调试器断点?可能是异步线程中发生了异常,并且在您的处理程序看到它之前正在处理它。好 q,顺便说一句,+1 -
@MartynA 谢谢!我有,但不幸的是,我无法在我自己的测试中重现该问题。
-
有什么有用的东西可以发送到
TADOConnection.OnInfoMessage事件吗? -
@Brian 简短回答:不。我已将事件连接起来以在
if EventStatus <> esOK条件下记录消息——因此esOK以外的任何状态都应记录整个消息;但是,没有任何记录。同样,OnExecuteComplete也有类似的功能,但当然也没有任何记录。由于在 6 小时内运行的查询数量庞大,删除此检查有点问题。
标签: delphi ado delphi-10.2-tokyo