Vala:通过处理程序ID从类析构函数断开信号处理程序的连接失败-为什么



我正试图弄清楚在Vala中类间信号的良好实践是什么。不知何故,似乎完全没有关于这个主题的文档。在学习了如何将信号微弱地连接到处理程序之后,问题仍然存在,当信号处理程序被破坏时,如何最好地断开连接。

让我们考虑一个简单的例子。我们有两个类,FooBar,其中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 finalizedn" );
}
private void handle() {
stdout.printf( "handler: %sn", 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
}

根据该注释,在Foo实例被销毁后,处理程序引用在bar_signal中仍然是一个悬空指针,因为我们还没有断开它的连接。

我已经考虑添加

bar.bar_signal.disconnect( handle );

Foo的析构函数,工作良好。

然而,我认为在某些情况下,handle可能无法从析构函数访问,例如,当处理程序是一个闭包时。

问题:我们还可以使用相应connect调用的返回值断开处理程序与信号的连接。例如,在connect调用之后立即执行此操作时,效果非常好。但为什么它会在析构函数中失败呢?

~Foo() {
stdout.printf( "foo finalizedn" );
bar.disconnect( hid ); // <--!!
}

出现以下提示时失败:

GLib GObject警告**:实例"0x8c8820"没有id为"1"的处理程序


更新

更新1:有趣的是,如果bar.disconnect( hid )调用是在Foodispose方法中完成的,那么它是有效的。根据GObject的内存管理文档,dispose是在对象最终确定之前调用的(我认为它对应于Vala中的析构函数),其目的是剪切对其他对象的所有引用值得注意的是,正如@AlThomas所指出的,"当信号处理程序用户数据被破坏时,信号处理程序[…]不会自动断开连接。">那么,为什么我们可以断开信号处理程序与dispose的连接,而不是在Foo最终确定时,这正是GObject文档实际建议的?

我想到的唯一解释是:与我之前被告知的相反,Vala/GLib在处理和最终确定对象之间自行断开信号处理程序。然而,这听起来不太可能,所以我真的很感激进一步的澄清。

更新2:事实证明,上面并没有那么错误:)

一般来说,Vala的信号是使用GLib的GObject信号的观测器模式的实现。因此,GLib自己的文档是一个很好的信息来源。例如信号和如何创建和使用信号。后者说明:

GType中的信号系统非常复杂和灵活:它的用户可以在运行时将任何数量的回调(以存在绑定的任何语言实现)连接到任何信号,并在信号发射过程的任何状态下停止任何信号的发射。

一个复杂的主题需要良好的文档,您的Vala特定问题有助于建立该主题的知识体系。

对于您的问题,GLib文档中最相关的部分可能是信号处理程序的内存管理。本节建议:

当发出信号的对象完成时,信号处理程序会自动断开连接,但当信号处理程序用户数据被破坏时,它们不会自动断开。。。管理此类用户数据有两种策略。第一种是在用户数据(对象)完成时断开信号处理程序(使用g_signal_handler_disconnect()g_signal_handlers_disconnect_by_func());这必须手动实现。对于非线程程序,g_signal_connect_object()可以用来自动实现。。。第二是要有一个强有力的参考。。。建议采用第一种方法。。。

深入了解Vala的工作方式时,一个有用的选项是带有valac--ccode开关。如果您查看程序中的C代码,您将看到foo_construct函数包含对g_signal_connect_object()的调用。因此,Vala使用文档中概述的第一种方法,但在建议的三个函数调用中,Vala通过调用g_signal_connect_object()使用自动方法。

没有必要手动断开连接,这就是您收到警告的原因。GLib已断开信号。GLib文档在这里可能有点混乱,因为手动过程的一部分有一个可以自动断开连接的调用。

GLib关于对象销毁的文档建议"对象的销毁过程分为两个阶段:处置和最终确定"。因此,假设断开连接是在处置阶段完成的,但您必须查看GLib的源代码才能确认这一点。

最新更新