Vala: Disconnecting signal handler via Handler ID from class destructor fails - Why?

主宰稳场 提交于 2019-12-11 05:29:37

问题


I'm trying to get my head around what a good practice for inter-class signals in Vala is. Somehow, there seems to be absolutely no documentation available on this topic. After learning how to connect signals to handlers weakly, the question remains, how that connection is best to be dropped, when the signal handler is destroyed.

Lets consider a simple example. We have two classes, Foo and Bar, where an instance of Bar emits a signal bar_signal, that is handled by an instance of Foo. However, this signal is connected weakly, allowing the Foo instance do be deleted, why the connection is still in tact:

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
}

According to that comment, the handler reference remains as a dangling pointer in bar_signal, after the Foo instance is destroyed, because we aren't disconnecting it yet.

I have considered adding a

bar.bar_signal.disconnect( handle );

the destructor of Foo, which works fine.

However, I think that there might be occasions, where handle isn't accessible from the destructor, e.g. when the handler is a closure.

Question: We also can disconnect the handler from the signal using the return value of the corresponding connect call. This works very fine, when done right after the connect call for example. But why does it fail in the destructor?

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

It fails with the following hint:

GLib-GObject-WARNING **: instance '0x8c8820' has no handler with id '1'


Updates

Update 1: Interestingly, the bar.disconnect( hid ) invocation works, if it is done from within of Foo's dispose method. According to the documentation of GObject's memory management, dispose is called right before the object is finalized (which I think corresponds to the destructor in Vala) and it's intention is to cut all references to other objects. Notably, as pointed out by @AlThomas, "signal handlers [...] are not automatically disconnected when the signal handler user data is destroyed." So why can we disconnect the signal handler from dispose, but not when Foo is finalized, which is what GObject's documentation actually recommends?

The only explanation, that comes to my mind, is that: Contrary to what I've been told before Vala/GLib do disconnect the signal handlers on it's own in between of disposing and finalizing the object. However, this sounds quite unlikely to me, so I would really appreciate further clarification.

Update 2: Turns out, the above wasn't that wrong :)


回答1:


As a general note, Vala's signals are an implementation of the observer pattern using GLib's GObject signals. So a good source of information is GLib's own documentation. For example Signals and How to create and use signals. The latter stating:

The signal system in GType is pretty complex and flexible: it is possible for its users to connect at runtime any number of callbacks (implemented in any language for which a binding exists) to any signal and to stop the emission of any signal at any state of the signal emission process.

A complex topic needs good documentation and your Vala specific questions help to build up the body of knowledge on this topic.

For your question the most relevant section of the GLib documentation is probably Memory management of signal handlers. This section advises:

While signal handlers are automatically disconnected when the object emitting the signal is finalised, they are not automatically disconnected when the signal handler user data is destroyed...There are two strategies for managing such user data. The first is to disconnect the signal handler (using g_signal_handler_disconnect() or g_signal_handlers_disconnect_by_func()) when the user data (object) is finalised; this has to be implemented manually. For non-threaded programs, g_signal_connect_object() can be used to implement this automatically...The second is to hold a strong reference...The first approach is recommended...

When digging deep in to how Vala works a useful option is the --ccode switch with valac. If you look at the C code from your program you will see the foo_construct function includes a call to g_signal_connect_object(). So Vala is using the first approach outlined in the documentation, but of the three function calls suggested Vala is using the automatic one by calling g_signal_connect_object().

There is no need to disconnect manually and this is why you are getting the warning. GLib has already disconnected the signal. The GLib documentation may be a bit confusing here, because part of the manual process has a call that can automatically disconnect.

GLib's documentation on Object destruction advises "The destruction process of your object is in two phases: dispose and finalize". So presumably the disconnect is done in the dispose stage, but you would have to look at GLib's source code to confirm that.



来源:https://stackoverflow.com/questions/39034280/vala-disconnecting-signal-handler-via-handler-id-from-class-destructor-fails

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!