2

我很难理解为什么 XPObserver.pas 中的 TXPSubject.DeleteObserver 和 TXPSubject.DeleteObservers 的实现不同的原因?具体来说,“Dispose”和“ReleaseSubject”调用的不同顺序,以及不同顺序对 TXPSubject.DeleteObserver 很重要的原因。有问题的代码被提取并显示在下面。该文件可从 DUnit 获得。

function TXPSubject.DeleteObserver(const Observer: IXPObserver;
  const Context: pointer): boolean;
var
  idx: integer;
  ObserverInfo: TXPObserverInfo;

begin
  FSync.Enter;

  try
    // Check for existence or prior removal
    Result := FindObserver(Observer, Context, idx);

    if Result then
    begin
      // Need local ref after deletion from list. Order of Delete() &
      // ReleaseSubject() is important here for correct functioning of _Release
      // ...***DON'T*** refactor this method!!
      ObserverInfo := PXPObserverInfo(FObservers[idx])^;
      // Release our (list) reference to observer
      PXPObserverInfo(FObservers[idx])^.Observer := nil;
      System.Dispose(FObservers[idx]);
      FObservers.Delete(idx);
    end;

    // Exit critical section here as we now have local vars only (thread-safe)
    // and call to ReleaseSubject below on last reference will leave FSync
    // invalid (destroyed).
  finally
    FSync.Leave;
  end;

  // Notify Observer to release reference to us. This will result in
  // a call to TXPSubject._Release.
  if Result then
    ObserverInfo.Observer.ReleaseSubject(IXPSubject(ObserverInfo.Subject),
      ObserverInfo.Context);

end;


procedure TXPSubject.DeleteObservers;
var
  idx: integer;
  ObserverInfo: PXPObserverInfo;

begin
  FDeletingObservers := true;
  // Count *down* to allow for side-effect of loop actions -
  // referenced item will be deleted from list, and remainder will move down
  // one slot.
  for idx := FObservers.Count - 1 downto 0 do
  begin
    ObserverInfo := FObservers[idx];
    // Notify Observer to release reference to Subject
    ObserverInfo^.Observer.ReleaseSubject(IXPSubject(ObserverInfo.Subject),
      ObserverInfo^.Context);
    // Release our (list) reference to Observer
    ObserverInfo^.Observer := nil;
    System.Dispose(ObserverInfo);
    FObservers.Delete(idx);
  end;

  FDeletingObservers := false;
end;
4

2 回答 2

2

第二种方法是更明显的实现,它不关心线程安全。第一个是棘手的。

第一种方法使用临界区FSync来保证线程安全。由于TXPSubjectis a TInterfacedObject(在提供的代码中看不到),因此在删除对该对象的最后一个引用时,它将被销毁。这意味着当使用DeleteObserverwhich 调用ReleaseSubject最后一个观察者(应该将对主题的引用设置为nil)删除最后一个观察者时,该对象将被销毁。现在,如果ReleaseSubject在锁内调用了,那么该行

FSync.Leave;

导致访问冲突,因为对象(因此也是FSync)已被销毁。

因此,不是从锁中调用,而是ReleaseSubject使用创建观察者信息的本地副本

ObserverInfo := PXPObserverInfo(FObservers[idx])^;

这取消了存储在观察者列表中的指针,基本上创建了一个本地副本。然后,在FSync锁被释放后,ReleaseSubject在该本地副本上调用。之后Self不再引用该点,因此该对象已被销毁并不重要。

编辑:调用的人DeleteObserver仍然持有一个有效的引用,ISubject所以在这个引用超出范围之前它不应该被销毁。因此,此答案中描述的场景仅在观察者本身调用DeleteObserver(可能在其析构函数中)对稍后由ReleaseSubject.

于 2013-11-13T07:25:30.717 回答
2

1)DeleteObserverDeleteObservers分别用于不同的上下文,客户端和服务器。事后看来,我现在将IXPSubject界面简化为主题的客户端界面

观察者用来注册和注销对主题的兴趣的客户端接口:

  IXPSubject = interface(IXPObserver)
    ['{D7E3FD5D-0A70-4095-AF41-433E7E4A9C29}']
    function AddObserver(const Observer: IXPObserver;
      const Subject: IXPSubject; const Context: pointer = nil): boolean;
    function InsertObserver(const idx: integer; const Observer: IXPObserver;
      const Subject: IXPSubject; const Context: pointer = nil): boolean;
    function DeleteObserver(const Observer: IXPObserver;
      const Context: pointer = nil): boolean;

    function ObserverCount: integer;
    function GetObserver(const idx: integer): IXPObserver;
    property Observers[const idx: integer]: IXPObserver read GetObserver;
    property Count: integer read ObserverCount;
  end;

DeleteObservers仅由 间接调用TXPSubject._Release。此方法不使用临界区FSync,因为它的使用 inside_Release已被 保护FSyncDeleteObservers应该是实现类的私有方法,IXPSubjectTXPSubject

您会注意到DeleteObserversTXPSubject._Release. 这是在任何观察者注册之前创建的对主题的主要引用被释放的时候。通常,这将是在主体的容器对象被销毁时。

2) 方法的排序

TXPSubject.DeleteObserver

  // Release our (list) reference to observer
  PXPObserverInfo(FObservers[idx])^.Observer := nil;
  System.Dispose(FObservers[idx]);
  FObservers.Delete(idx);
  ObserverInfo.Observer.ReleaseSubject(IXPSubject(ObserverInfo.Subject),
    ObserverInfo.Context);

TXPSubject.DeleteObservers

  // Notify Observer to release reference to Subject
  ObserverInfo^.Observer.ReleaseSubject(IXPSubject(ObserverInfo.Subject),
    ObserverInfo^.Context);
  // Release our (list) reference to Observer
  ObserverInfo^.Observer := nil;
  System.Dispose(ObserverInfo);

观察者发起的注销

如前所述,Subject.DeleteObserver当观察者希望从主题中注销自己时,它由观察者调用 - 当观察者启动注销过程时。这样做的效果是,Subject 在调用(临时线程本地副本)之前完成其内部状态(删除对 Observer 的引用)Observer.ReleaseSubject

Observer.ReleaseSubject中,观察者对 Subject 的引用是 nil'd,间接导致调用Subject._Release...

TXPSubject._Release检查是否正在释放对自身的主要引用。如果我们在调用之前没有最终确定 Subject 的内部状态Observer.ReleaseSubject,我们现在会错误地调用DeleteObservers。这种情况下的正确行为要求ReleaseSubject最后调用。

主体发起的注销

主体在释放主要参考时启动注销。如前所述,这通常是在主体的容器对象被销毁时,或者单例(可能是单元范围或全局)引用在应用程序关闭时被销毁。

我们可以在 中看到TXPSubject._ReleaseDeleteObservers在这种情况下会调用。当观察者释放他们对 Subject 的引用间接调用时,该标志FDeletingObservers设置在 entry toDeleteObservers以防止重新进入TXPSubject._Release

Subject 必须具有对 Observer 的引用才能调用Observer.ReleaseSubject,因此内部状态的最终确定发生调用Observer.ReleaseSubject in之后DeleteObservers

我希望这有助于澄清你的情况,李?

干杯,保罗。

于 2013-11-15T06:57:55.623 回答