我正试图了解Vala中类间信号的良好做法。不知何故,似乎没有关于这个主题的文档。在学习了如何connect signals to handlers weakly后,问题仍然存在,当信号处理程序被销毁时,如何最好地删除该连接。
让我们考虑一个简单的例子。我们有两个类Foo
和Bar
,其中Bar
的实例发出信号bar_signal
,由Foo
的实例处理。但是,此信号是弱连接的,允许删除Foo
实例,为什么连接仍然有效:
class Bar : Object {
public signal void bar_signal();
}
class Foo : Object {
string tag;
ulong hid;
Bar bar;
public Foo( Bar bar, string tag ) {
this.tag = tag;
this.bar = bar;
weak Foo weak_this = this;
hid = bar.bar_signal.connect( weak_this.handle );
}
~Foo() {
stdout.printf( "foo finalized\n" );
}
private void handle() {
stdout.printf( "handler: %s\n", tag );
}
}
public static void main( string[] args ) {
Bar bar = new Bar();
{
Foo foo = new Foo( bar, "x" );
bar.bar_signal(); // writes "handler: x"
} // foo destroyed at end of block
bar.bar_signal(); // writes nothing
}
根据that comment,在bar_signal
实例被销毁之后,处理程序引用仍然是Foo
中的悬空指针,因为我们还没有断开它。
我考虑过添加
bar.bar_signal.disconnect( handle );
Foo
的析构函数,工作正常。
但是,我认为有时可能无法从析构函数访问handle
,例如当处理程序是一个闭包时。
问题:我们还可以使用相应connect
调用的返回值断开处理程序与信号的连接。例如,在connect
调用之后立即执行此操作非常正常。但是为什么它在析构函数中失败了?
~Foo() {
stdout.printf( "foo finalized\n" );
bar.disconnect( hid ); // <--!!
}
它失败并带有以下提示:
GLib-GObject-WARNING **:实例'0x8c8820'没有ID为'1'的处理程序
更新1:有趣的是,bar.disconnect( hid )
调用是有效的,如果是在Foo
的{{1}}方法内完成的话。根据{{3}}的文档,dispose
在对象完成之前被调用(我认为它对应于Vala中的析构函数),而的目的是剪切对其他对象的所有引用。 值得注意的是,正如@AlThomas所指出的那样,“信号处理程序[...]在信号处理程序用户数据被销毁时不会自动断开连接。”那为什么我们可以断开信号处理程序来自dispose
,但不是dispose
最终确定时,这是GObject的文档GObject
's memory management?
我想到的唯一解释是:与我在Vala / GLib 之前被告知的情况相反在处理和最终确定之间断开信号处理程序宾语。但是,这对我来说听起来不太可能,所以我非常感谢你进一步澄清。
更新2:原来,上面没有错::)
答案 0 :(得分:3)
作为一般说明,Vala的信号是使用GLib的GObject信号的observer pattern的实现。因此,GLib自己的文档是一个很好的信息来源。例如Signals和How to create and use signals。后者说:
GType中的信号系统非常复杂和灵活:用户可以在运行时将任意数量的回调(以任何语言实现绑定存在)连接到任何信号并停止发出任何信号在信号发射过程的任何状态。
复杂的主题需要良好的文档,而您的Vala特定问题有助于建立有关此主题的知识体系。
对于您的问题,GLib文档中最相关的部分可能是Memory management of signal handlers。本节建议:
当信号处理程序用户数据被销毁时,当信号处理程序用户数据被销毁时,信号处理程序会自动断开,而信号处理程序不会自动断开......管理此类用户数据有两种策略。第一种是在用户数据(对象)完成时断开信号处理程序(使用
g_signal_handler_disconnect()
或g_signal_handlers_disconnect_by_func()
);这必须手动实施。对于非线程程序,可以使用g_signal_connect_object()
自动实现这个...第二个是保留一个强引用...建议采用第一种方法......
在深入了解Vala的工作原理时,--ccode
切换为valac
。如果您查看程序中的C代码,您会看到foo_construct
功能包含对g_signal_connect_object()
的调用。所以Vala正在使用文档中概述的第一种方法,但是在三个函数调用中,Vala通过调用g_signal_connect_object()
来使用自动调用。
无需手动断开连接,这就是您收到警告的原因。 GLib已经断开了信号。这里的GLib文档可能有点令人困惑,因为部分手动过程有一个可以自动断开的调用。
关于Object destruction的GLib文档建议&#34;对象的销毁过程分为两个阶段:处理和完成&#34;。所以推测断开是在处理阶段完成的,但你必须查看GLib的源代码来确认。