销毁类实例并不会终止它在vala中拥有的实例
我有一个Gtk.Box的子类,它包含一个GLib.Timer,它在给定时间间隔后触发通知。我在这个类中调用了Gtk.Box上的this.destroy()方法。计时器继续运行,即使在其父实例被销毁后也会触发通知。已经销毁的这个类的所有实例都表现出这种行为并继续使用CPU和内存,直到进程终止。销毁类实例并不会终止它在vala中拥有的实例
我该如何解决这个问题?如何有效杀死实例,以及如何手动释放内存而不是依赖vala的垃圾回收。
编辑:这里是一个(尴尬)mvce
// mvce_deletable
// nine
// 2017.01.11
// valac --pkg gtk+-3.0 --pkg glib-2.0 deletablebox.vala
using Gtk;
using GLib;
class RemovableBox : Gtk.Box {
private Gtk.Button delete_button;
private GLib.Timer timer;
private Gtk.Label label;
public RemovableBox() {
delete_button = new Gtk.Button.with_label ("DESTROY");
delete_button.clicked.connect (()=>{this.destroy();});
this.add (delete_button);
label = new Gtk.Label ("0000000");
this.add (label);
timer = new GLib.Timer();
timer.start();
Timeout.add (50, update);
this.show_all();
}
private bool update() {
if (timer.elapsed() > 10.0f) {
stdout.printf("and yet it breathes\n");
}
label.set_text ("%f".printf(timer.elapsed()));
return true;
}
}
int main (string [] args) {
Gtk.init(ref args);
var window = new Gtk.Window();
window.destroy.connect (Gtk.main_quit);
var delete_me = new RemovableBox();
window.add (delete_me);
window.show_all();
Gtk.main();
return 0;
}
我加了一个timer_id的的RemovableBox类,但它仍期望不起作用。
class RemovableBox : Gtk.Box {
private Gtk.Button delete_button;
private uint timeout_id;
private GLib.Timer timer;
private Gtk.Label label;
public RemovableBox() {
delete_button = new Gtk.Button.with_label ("DESTROY");
delete_button.clicked.connect (()=>{this.destroy();});
this.add (delete_button);
label = new Gtk.Label ("0000000");
this.add (label);
timer = new GLib.Timer();
timer.start();
timeout_id = Timeout.add (40, update);
this.show_all();
}
~ RemovableBox() {
Source.remove (timeout_id);
}
private bool update() {
if (timer.elapsed() > 10.0f) {
stdout.printf("and yet it breathes\n");
}
label.set_text ("%f".printf(timer.elapsed()));
return true;
}
}
你正在混淆自动引用couting与完整的垃圾回收。
GLib中没有垃圾收集器,但类有一个引用计数,而当GObject在多个位置使用时会增加引用计数,而当这些位置不再使用时会减少,直到达到零。该对象然后释放。
实际上在C代码引用计数是手册:
// Reference count is set to 1 on gobject_new
gpointer obj = gobject_new (G_SOME_OBJECT, NULL);
// It can be manually increased when the object is stored in multiple places
// Let's increase the ref count to 2 here
gobject_ref (obj);
// Let's decrease it until it reaches 0
gobject_unref (obj);
gobject_unref (obj);
// Here the object is destroyed (but the pointer is still pointing to the previous memory location, e.g. it is a dangling pointer)
// gobject_clear (obj) can be used in situation where the variable is reused
// It still only unrefs the object by 1 though! In addition it will set obj to NULL
伐拉添加auto
引用计数,这使得它的“自动引用计数”(ARC)。那就是你不必担心Vala中的引用计数,它会为你添加适当的ref
和unref
操作。在完整的垃圾收集(如在C#,Java,...)中,内存释放不是确定性的,即使它不再被使用,对象也可以保持活动状态。这是通过使用称为“托管堆”的东西完成的,垃圾收集器在后台运行(即作为GC线程)。
现在,我们有后台的东西覆盖您的实际问题:
您必须删除Gtk.Box从它的父容器,也可以设置您可能还是会在你的瓦拉代码null
以任何引用将引用计数设置为0.然后将其编号为unref
。
当然还有其他选项,如禁用计时器等。您应该真的为您的问题添加一个MVCE,以便我们能够为您提供一些关于您的代码的设计建议。
PS:引用计数通常被认为是一种简单的垃圾收集方法。这就是为什么我写完整垃圾回收(也称为tracing garbage collection
),以免混淆这两个术语。 See the Wikipedia article on garbage collection。
GLib.Timer
是一个秒表,返回经过的时间。它不会生成事件,但GLib.Timeout
会。
GLib使用事件循环。对于使用相同的底层GLib事件循环的GTK +也是如此。 GLib.Timeout
用于创建一种事件源 - 在给定时间间隔后触发的计时器。当您的程序创建事件源时,您将获得源的标识符。例如:
timer_id = Timeout.add_seconds (1, my_callback_function);
什么程序需要做的是存储在对象,然后在按一下按钮处理程序被调用,您可以删除计时器作为事件的来源,定时器标识符:
Source.remove (timer_id);
严格地说,Vala没有垃圾收集周期。其他语言将收集不再使用的引用,然后在清理周期中删除分配给它们的资源。 Vala使用引用计数,但它是确定性的。所以当一个对象不再被使用时,通常当它超出作用域时,分配给该对象的资源立即被移除。对于Vala中的普通对象而不是紧凑类,当对象被释放时也会调用析构函数。这允许资源分配是在Vala中有效使用的初始化(RAII)模式。
一般来说,你不应该手动释放对象,Vala的引用计数是非常好的。我认为了解GLib的事件循环和事件来源对了解发生的事情非常重要。有关详细说明,请参见GLib's documentation on its main event loop。
现在您已经提供了一个MCVE我们可以详细了解Vala如何管理内存。如果您想深入了解幕后发生的情况,您可以使用--ccode
开关valac
。
在你的计划感兴趣的第一行是:
Timeout.add (50, update);
从valac
望着C代码此行使用g-timeout-add-full()
功能:
g_timeout_add_full (G_PRIORITY_DEFAULT, (guint) 50, _removable_box_update_gsource_func, g_object_ref (self), g_object_unref);
的重要组成部分在这里是g_object_ref (self)
。这会将对象的引用计数增加1,并将指针传递给对象。这很有意义,因为在Vala代码中传递的update()
回调利用了来自对象的实例数据。 Vala正在做正确的事情,并确保实例数据在计时器周围保持活跃状态。当源被移除时,会调用'g_object_unref'。这是你的程序把这个认识到实践的修改版本:
// mvce_deletable
// nine
// 2017.01.11
// valac --pkg gtk+-3.0 deletablebox.vala
using Gtk;
class RemovableBox : Gtk.Box {
private Gtk.Button delete_button;
private uint timeout_id;
private GLib.Timer timer;
private Gtk.Label label;
public RemovableBox() {
delete_button = new Gtk.Button.with_label ("DESTROY");
delete_button.clicked.connect (()=>{this.tidy_up_and_destroy();});
this.add (delete_button);
label = new Gtk.Label ("0000000");
this.add (label);
timer = new GLib.Timer();
timer.start();
timeout_id = Timeout.add (40, update);
this.show_all();
}
~ RemovableBox() {
print ("RemovableBox destructor called\n");
}
private bool update() {
if (timer.elapsed() > 10.0f) {
stdout.printf("and yet it breathes\n");
}
label.set_text ("%f".printf(timer.elapsed()));
return true;
}
private void tidy_up_and_destroy() {
print ("RemovableBox.tidy_up_and_destroy called\n");
Source.remove (timeout_id);
this.destroy();
}
}
void main (string [] args) {
Gtk.init(ref args);
var window = new Gtk.Window();
window.window_position = WindowPosition.CENTER;
window.resize (250,50);
window.destroy.connect (Gtk.main_quit);
window.add (new RemovableBox());
window.show_all();
Gtk.main();
}
此前该程序仍保持了RemovableBox
对象的引用,因此从未被完全除去。通过首先删除事件源然后调用this.destroy();
它意味着没有更多的引用和对象被删除。
这里还有一点很重要。该行:
var delete_me = new RemovableBox();
window.add (delete_me);
在
main()
已更改为:
window.add (new RemovableBox());
瓦拉对象他们在被创建的块的范围存在通过分配对象delete_me
你保持一个参考该对象为main()
块的其余部分。通过将其更改为方法调用的参数,它仅用于调用,因此在单击按钮时释放。
顺便说一句,当使用valac
时,GLib
被自动包含,所以不需要using GLib;
或编译--pkg glib-2.0
。
我甚至没有意识到Vala的RAII到现在为止。在堆栈上也分配了非GObject类(如在C++中)? –
维基百科文章引用了C++和堆栈。我认为这对于一般的RAII概念有点误导。在Vala中,释放资源的调用是由Vala编译器以与创建它们相反的顺序创建的。所以你可以在try块中引发一个“异常”,并且析构函数会反向调用,这样就可以正确释放资源。这是另一个问题,虽然:) – AlThomas
所以我宣布timer_id作为一个uint和修改类像你说的,但它仍然不能按需要工作。 – Nine