Delphi DUnit'中TXPSubject.DeleteObserver和TXPSubject.DeleteObserver之间存在差异的原因;是XPObserver.pas吗?

Delphi DUnit'中TXPSubject.DeleteObserver和TXPSubject.DeleteObserver之间存在差异的原因;是XPObserver.pas吗?,delphi,interface,observer-pattern,dunit,Delphi,Interface,Observer Pattern,Dunit,我发现很难理解为什么XPObserver.pas中TXPSubject.DeleteObserver和TXPSubject.DeleteObserver的实现不同?具体来说,“Dispose”和“ReleaseSubject”调用的不同顺序,以及为什么不同顺序对TXPSubject.DeleteObserver很重要的原因。下面提取并显示了相关代码。该文件可从DUnit获得 function TXPSubject.DeleteObserver(const Observer: IXPObserve

我发现很难理解为什么XPObserver.pas中TXPSubject.DeleteObserver和TXPSubject.DeleteObserver的实现不同?具体来说,“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;

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

第一种方法使用一个关键部分
FSync
来保证线程安全。由于
TXPSubject
是一个
TInterfacedObject
(在提供的代码中无法看到),因此在删除对该对象的最后一次引用时,它将被销毁。这意味着,当使用
DeleteObserver
删除最后一个观察者时,该观察者调用该最后一个观察者的
ReleaseSubject
(该观察者应将对该观察者的引用设置为
nil
),该对象将被销毁。现在,如果在锁中调用了
ReleaseSubject
,那么

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

因此,不再从锁中调用
ReleaseSubject
,而是使用

ObserverInfo := PXPObserverInfo(FObservers[idx])^;
这将取消对存储在观察者列表中的指针的引用,并基本上创建一个本地副本。然后,在解除
FSync
锁后,对该本地副本调用
ReleaseSubject
。在该点之后,
Self
不再被引用,因此对象是否已被销毁并不重要


EDIT:调用
DeleteObserver
的人仍然持有对
ISubject
的有效引用,因此在该引用超出范围之前,它不应被销毁。因此,这个答案中描述的场景只有在观察者自己调用
DeleteObserver
(可能在其析构函数中)引用时才会发生,该引用后来被
ReleaseSubject

重新卷绕。第二种方法是更明显的实现,它不涉及线程安全。第一个是棘手的问题

第一种方法使用一个关键部分
FSync
来保证线程安全。由于
TXPSubject
是一个
TInterfacedObject
(在提供的代码中无法看到),因此在删除对该对象的最后一次引用时,它将被销毁。这意味着,当使用
DeleteObserver
删除最后一个观察者时,该观察者调用该最后一个观察者的
ReleaseSubject
(该观察者应将对该观察者的引用设置为
nil
),该对象将被销毁。现在,如果在锁中调用了
ReleaseSubject
,那么

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

因此,不再从锁中调用
ReleaseSubject
,而是使用

ObserverInfo := PXPObserverInfo(FObservers[idx])^;
这将取消对存储在观察者列表中的指针的引用,并基本上创建一个本地副本。然后,在解除
FSync
锁后,对该本地副本调用
ReleaseSubject
。在该点之后,
Self
不再被引用,因此对象是否已被销毁并不重要

EDIT:调用
DeleteObserver
的人仍然持有对
ISubject
的有效引用,因此在该引用超出范围之前,它不应被销毁。因此,只有当观察者自己调用
DeleteObserver
(可能在其析构函数中)引用(该引用随后由
ReleaseSubject

1)时,才会出现此答案中描述的场景。
DeleteObserver
DeleteObserver
分别用于不同的上下文(客户端和服务器)。事后诸葛亮,我现在将把
IXPSObject
接口简化为主题的客户端接口

客户端界面,供观察者用于注册和注销某个主题的兴趣:

  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.\u Release
间接调用。此方法不使用关键部分
FSync
,因为它在
\u Release
中的使用已由
FSync
保护。
DeleteObservers
应该是实现
IXPSObject
TXPSubject

您会注意到,
deleteObservators
TXPSubject.\u 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.ReleaseSubject

Observer.ReleaseSubject
中,观察者对Subject的引用为零,间接导致调用
Subject

TXPSubject.\u Release
检查对自身的主要引用是否已被释放。如果我们在调用
Observer.ReleaseS之前没有确定对象的内部状态