Delphi DUnit'中TXPSubject.DeleteObserver和TXPSubject.DeleteObserver之间存在差异的原因;是XPObserver.pas吗?
我发现很难理解为什么XPObserver.pas中TXPSubject.DeleteObserver和TXPSubject.DeleteObserver的实现不同?具体来说,“Dispose”和“ReleaseSubject”调用的不同顺序,以及为什么不同顺序对TXPSubject.DeleteObserver很重要的原因。下面提取并显示了相关代码。该文件可从DUnit获得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
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之前没有确定对象的内部状态