这是Rio上的System.Net.HttpClient中的错误吗?

时间:2019-04-25 20:56:54

标签: delphi firemonkey delphi-10.3-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中,然后将其引用计数减少一个。为什么将其引用计数减少一个? THTTPClient仍然有效,并使用了为什么破坏它的引用计数?他们是个虫子,也许这家伙打错了字,但我不明白他最初想做什么...

有关此信息的内容,如何从字典中删除:

procedure THTTPClientHelper.RemoveExt;
begin
  if FHTTPClientList = nil then
    Exit;
  TMonitor.Enter(FHTTPClientList);
  try
    FHTTPClientList.Remove(Self);
  finally
    TMonitor.Exit(FHTTPClientList);
  end;
end;

1 个答案:

答案 0 :(得分:2)

以上代码中针对ARC编译器的手动引用计数的目的是模拟带有弱引用的字典。 Delphi泛型集合以泛型数组作为后盾,泛型数组将对添加到ARC编译器上的集合的任何对象保持强烈的引用。

有几种方法可以实现弱引用-使用指针,在对象被声明为弱对象的对象周围使用包装器并在适当的位置进行手动引用计数。

使用指针会丢失类型安全性,因此包装器需要更多的代码,因此我想上述代码的作者选择了手动引用计数。那部分没问题。

但是,正如您所注意到的那样,该代码中有些混乱-当SetExt例程正确编写时,RemoveExt有一个错误,导致稍后崩溃。

让我们遍历ARC编译器上下文中的代码(为简便起见,我将省略编译器指令和无关的代码):

由于将对象添加到集合(数组)中会增加引用计数,因此要实现弱引用,我们必须减少添加的对象实例的引用计数-这样,实例的引用计数在存储到集合中后将保持不变。接下来,当我们从此类集合中删除对象时,我们必须恢复参考计数余额并增加参考计数。另外,我们必须确保在销毁对象之前将其从此类集合中删除-这样做的好地方是析构函数。

添加到收藏集:

LRelease := not FHTTPClientList.ContainsKey(Self);
FHTTPClientList.AddOrSetValue(Self, LExt);
if LRelease then __ObjRelease;

我们将对象添加到集合中,然后在集合对我们的对象拥有强烈的引用之后,我们可以释放它的引用计数。如果对象已经在集合中,则意味着它的引用计数已经减少,我们不能再次减少它-这是LRelease标志的目的。

从收藏夹中删除:

if FHTTPClientList.ContainsKey(Self) then
  begin
    __ObjAddRef;
    FHTTPClientList.Remove(Self);
  end;

如果对象在集合中,则必须从集合中删除对象之前恢复余额并增加引用计数。这是RemoveExt方法中缺少的部分。

确保销毁对象不在列表中

destructor THTTPClient.Destroy;
begin
  RemoveExt;
  inherited;
end;

注意:为了使此类伪造的弱集合正常工作,必须仅通过上述方法来平衡引用计数,从而添加和删除项目。使用任何其他原始收集方法(例如Clear)都会导致引用计数中断。


是不是?

System.Net.HttpClient代码中, RemoveExt方法仅在析构函数中调用,FHTTPClientList也是私有变量,不会以任何其他方式更改。乍一看,该代码可以正常运行,但实际上包含了相当细微的错误。

要弄清实际错误,我们需要从几个已确定的事实开始,涵盖可能的使用场景:

  1. FHTTPClientListSetExt方法中只有改变内容并根据RemoveExt词典中的项目的引用计数的方法
  2. SetExt方法正确
  3. 仅在RemoveExt析构函数中调用未调用__ObjAddRef的残破THTTPClient方法,这是此 sub 错误的起源。

当在任何特定的对象实例上调用析构函数时,这意味着该对象实例已达到生存期,并且任何后续引用计数触发器(在析构函数执行期间)都不会影响代码的正确性。

这是通过在变量objDestroyingFlag上应用FRefCount来更改其值来确保的,任何进一步的计数增加/减少都不再导致开始破坏过程的特殊值0-因此对象是安全,不会被摧毁两次。

在上面的代码中,当调用THTTPClient析构函数时,这意味着对对象实例的最后一个强引用已超出范围或被设置为nil,并且此时唯一可以触发引用的剩余活动引用计数机制是FHTTPClientList中的一种。如前所述,该引用已通过RemoveExt方法清除(无论是否中断),如前所述。而且一切正常。

但是,代码的编写者忘记了一个触发析构函数的微小的东西-DisposeOf方法,但是此时对象实例尚未达到其引用计数生存期。换句话说-如果DisposeOf调用了析构函数,则任何后续引用计数触发必须保持平衡,因为仍然存在对对象的实时引用,这些对象将在析构函数链调用后触发引用计数机制完成。如果我们在那个时候中断计数,结果将是灾难性的。

由于THTTPClient并非要求的TComponent后裔 DisposeOf,因此很容易进行疏忽而忘记有人在某个地方可以打电话给{{1} }仍然会在此类变量上使用}-例如,如果您创建了DipsoseOf个实例的拥有列表,则清除此类列表将在它们上调用THTTPClient并愉快地破坏其引用计数,因为DisposeOf方法最终被破坏了。 / p>

结论:是的,它是一个错误。