
[英]Delphi System.net.HTTPClient: Error reading data (12002) The operation timed out
[英]Is this a bug in System.Net.HttpClient on Rio?
这是在System.Net.HttpClient
Delphi Rio中找到的功能
THTTPClientHelper = class helper for THTTPClient
....
procedure THTTPClientHelper.SetExt(const Value);
var
{$IFDEF AUTOREFCOUNT}
LRelease: Boolean;
{$ENDIF}
LExt: THTTPClientExt;
begin
if FHTTPClientList = nil then
Exit;
TMonitor.Enter(FHTTPClientList);
try
{$IFDEF AUTOREFCOUNT}
LRelease := not FHTTPClientList.ContainsKey(Self);
{$ENDIF}
LExt := THTTPClientExt(Value);
FHTTPClientList.AddOrSetValue(Self, LExt);
{$IFDEF AUTOREFCOUNT}
if LRelease then __ObjRelease;
{$ENDIF}
finally
TMonitor.Exit(FHTTPClientList);
end;
end;
那家伙在这里尝试使用LRelease
做什么?
{$IFDEF AUTOREFCOUNT}
LRelease := not FHTTPClientList.ContainsKey(Self);
{$ENDIF}
LExt := THTTPClientExt(Value);
FHTTPClientList.AddOrSetValue(Self, LExt);
{$IFDEF AUTOREFCOUNT}
if LRelease then __ObjRelease;
{$ENDIF}
因此,如果FHTTPClientList
不包含THTTPClient
则将其添加到FHTTPClientList
,然后将其 FHTTPClientList
减一 。 为什么将其引用计数减少一个? THTTPClient
仍然有效,并使用了为什么破坏它的refcount? 他们是个虫子,也许那个人打错了字,但我不明白他本来想做什么...
对于信息,这是如何从字典中删除项目的:
procedure THTTPClientHelper.RemoveExt;
begin
if FHTTPClientList = nil then
Exit;
TMonitor.Enter(FHTTPClientList);
try
FHTTPClientList.Remove(Self);
finally
TMonitor.Exit(FHTTPClientList);
end;
end;
上面代码中ARC编译器的手动引用计数的目的是模拟带有弱引用的字典。 Delphi泛型集合以泛型数组为后盾,泛型数组将对添加到ARC编译器上的集合的任何对象保持强烈的引用。
有几种方法可以实现弱引用-使用指针,在对象被声明为弱对象的对象周围使用包装器以及在适当位置进行手动引用计数。
有了指针,您就失去了类型安全性,包装程序需要更多的代码,因此我想上述代码的作者选择了手动引用计数。 那部分没错。
但是,正如您所注意到的那样,该代码中SetExt
-在正确编写SetExt
例程的同时, RemoveExt
存在一个错误,导致稍后崩溃。
让我们在ARC编译器的上下文中浏览代码(为简便起见,我将省略编译器指令和无关的代码):
由于将对象添加到集合(数组)中会增加引用计数,因此要实现弱引用,我们必须减少添加的对象实例的引用计数-这样,实例的引用计数在存储到集合中后将保持不变。 接下来,当我们从此类集合中删除对象时,我们必须恢复参考计数余额并增加参考计数。 同样,我们必须确保在销毁对象之前将其从此类集合中删除-这样做的好地方是析构函数。
添加到收藏夹:
LRelease := not FHTTPClientList.ContainsKey(Self);
FHTTPClientList.AddOrSetValue(Self, LExt);
if LRelease then __ObjRelease;
我们将对象添加到集合中,然后在集合对我们的对象拥有强大的引用之后,我们可以释放它的引用计数。 如果object已经在collection内部,则意味着它的引用计数已经减少,我们不能再次减少它-这是LRelease
标志的目的。
从收藏中删除:
if FHTTPClientList.ContainsKey(Self) then
begin
__ObjAddRef;
FHTTPClientList.Remove(Self);
end;
如果对象在集合中,则必须从集合中删除对象之前恢复余额并增加引用计数。 这是RemoveExt
方法缺少的部分。
确保该对象不在销毁列表中:
destructor THTTPClient.Destroy;
begin
RemoveExt;
inherited;
end;
注意:为了使此类伪造的弱集合正常工作,必须仅通过上述方法来平衡引用计数,从而添加和删除项目。 使用其他任何原始收集方法(如“ Clear
都会导致引用计数中断。
有没有bug?
在System.Net.HttpClient
代码中,只有在析构函数中才调用损坏的 RemoveExt
方法, FHTTPClientList
也是私有变量,并且不会以任何其他方式更改。 乍一看,该代码可以正常工作,但实际上包含了相当细微的错误。
为了揭示真正的错误,我们需要从几个已确定的事实开始,涵盖可能的使用场景:
FHTTPClientList
词典中的项目的引用计数的方法是SetExt
和RemoveExt
方法 SetExt
方法正确 THTTPClient
析构函数中调用不调用__ObjAddRef
损坏的RemoveExt
方法,这是此细微错误的来源。 当在任何特定的对象实例上调用析构函数时,这意味着该对象实例已达到生存期,并且任何后续引用计数触发器(在析构函数执行期间)都不会影响代码的正确性。
通过在FRefCount
变量上应用objDestroyingFlag
来更改其值来确保这一点,并且任何进一步的计数增加/减少都不会再导致特殊值0
来启动销毁过程-因此对象是安全的,不会被销毁两次。
在上面的代码中,当THTTPClient
析构函数时,这意味着对对象实例的最后一个强引用已超出范围或设置为nil
,并且此时唯一可以触发引用计数机制的剩余活动引用就是FHTTPClientList
中的FHTTPClientList
。 该引用已通过RemoveExt
方法清除(无论是否断开),如先前所说的无关紧要。 而且一切正常。
但是,代码的编写者忘记了一个小小的东西-触发析构函数的DisposeOf
方法,但是此时对象实例尚未达到其引用计数生存期。 换句话说-如果DisposeOf
调用了析构函数,则必须平衡所有后续引用计数触发器,因为仍然存在对对象的实时引用,这些对象将在析构函数链调用完成后触发引用计数机制。 如果我们在那个时候中断计数,结果将是灾难性的。
由于THTTPClient
不是TComponent
需要后代DisposeOf
很容易使监督和忘记有人在什么地方可以调用DipsoseOf
反正在这样的变量-如果你把所有的名单例如THTTPClient
情况下,清除该名单将调用DisposeOf
他们,高兴地破坏其引用计数,因为RemoveExt
方法最终被破坏。
结论:是的,这是一个错误。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.