查找和删除侦听器



我正在尝试为可以在窗口或无头模式下运行的 MATLAB 应用程序设计一个单元测试。该测试以无头模式运行整个程序,并尝试检测在此过程中是否打开了任何窗口。

我的想法是将侦听器附加到groot属性CurrentFigure,并编写一个PostSet回调,该回调递增一个跟踪打开窗口数的计数器。最后,测试确保该值为 0。为了记录,这似乎不起作用。即使我有ShowHiddenHandles'on',它似乎也没有捕捉到模态窗口。但是,这不是我的问题。

我的问题是,我在创建侦听器的位置和delete侦听器的位置之间的测试代码中存在错误。现在,有一个侦听器附加到groot,但是侦听器句柄变量被清除了,所以每次我尝试打开窗口时,我都会得到非常奇怪的行为。测试对象重新打开,然后在侦听器回调时引发错误。

既然已从工作区中删除了附加到groot的所有原始引用,如何查找和删除附加到的侦听器?到目前为止,唯一有效的方法是重新启动 MATLAB,但这似乎是一种低效的调试方法。


若要重现错误,请创建以下测试类:

classdef MCVtest < matlab.unittest.TestCase
% Minimal, Complete, Verifiable example
properties
numOfFiguresCreated = 0;
end
methods
function figureCreatedListener(testCase)
testCase.numOfFiguresCreated = testCase.numOfFiguresCreated + 1;
end
end
methods (Test)
function testFiles(testCase)
%Create Listener for this particular input file.
listener = addlistener(groot, 'CurrentFigure', 'PostSet', @testCase.figureCreatedListener); %#ok<NASGU>
error('Well, this sucks...')
% delete Listener for this input file
delete(listener) %#ok<UNRCH>
% Verify That no graphics objects were created at all.
testCase.verifyEqual(testCase.numOfFiguresCreated, 0);
end
end
end

从命令行:

>> suite = matlab.unittest.TestSuite.fromClass(?MCVtest)
>> results = suite.run

错误后:

>> figure
Error using MCVtest/figureCreatedListener
Too many input arguments.
Error in MCVtest>@(varargin)testCase.figureCreatedListener(varargin{:}) (line 17)
listener = addlistener(groot, 'CurrentFigure', 'PostSet', @testCase.figureCreatedListener); %#ok<NASGU>

当然,listener已经不存在了,所以我无法删除它。我已经按顺序尝试了clearclear allclear classes,听众仍然存在。清除它的唯一方法(据我所知)是重新启动 MATLAB。

您应该能够通过访问Root(或任何其他图形)对象的未记录的'AutoListeners__'属性来找到侦听器,该对象包含侦听器的单元格数组。

例如:

a = addlistener(groot, 'CurrentFigure', 'PostSet', @(s,e)disp('hi'));
tmp = groot;
listeners = tmp.AutoListeners__;

这给了我们:

>> a == listeners{1}
ans =
logical
1

因此,我们可以执行以下操作:

for ii = 1:numel(listeners)
delete(listeners{ii});
end

删除所有悬空侦听器。

请注意,如果对象没有侦听器,则AutoListeners__不存在。


另请注意,MATLAB 有 2 种不同的侦听器实现:addlistener,它将侦听器绑定到对象并在对象超出范围时被删除,以及listener,我们取消绑定,当侦听器超出范围时将被删除。

通过在单元测试中使用listener而不是addlistener,如果清理由于某种原因(在本例中是由于错误)未运行,则可以避免侦听器悬而未决。

您可能需要考虑使用较低级别的接口来创建和管理侦听器,以便您永远不必再次解决此搁浅的侦听器状态问题。您可以将对侦听器的唯一引用绑定到所需的函数或对象生命周期范围,这样就不必显式删除侦听器,而是可以在最后一个引用被销毁后依靠引用计数来删除侦听器。

https://www.mathworks.com/help/matlab/ref/event.listener.html

https://www.mathworks.com/help/matlab/ref/event.proplistener.html

这些接口允许您在创建后将侦听器对象作为 LHS 参数接收。然后,您可以决定是否存储侦听器,以便可以根据需要管理其生命周期。

如果使用此接口,则可以避免显式使用 delete,因为可以将唯一侦听器句柄的生存期绑定到希望它存在的作用域。

因此,具体来说,在同一示例中:

lsnr = event.proplistener(groot,findprop(groot,'CurrentFigure'),'PostSet',@(hobj,evt) disp('fire'));

当 lsnr 超出范围时,对侦听器对象的最后一个引用将丢失,并且将自动删除 lsnr。当然,您也可以显式调用 delete。

最新更新