问题
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()
org_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