销毁类实例并不会终止它在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中的引用计数,它会为你添加适当的refunref操作。在完整的垃圾收集(如在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

+0

我甚至没有意识到Vala的RAII到现在为止。在堆栈上也分配了非GObject类(如在C++中)? –

+0

维基百科文章引用了C++和堆栈。我认为这对于一般的RAII概念有点误导。在Vala中,释放资源的调用是由Vala编译器以与创建它们相反的顺序创建的。所以你可以在try块中引发一个“异常”,并且析构函数会反向调用,这样就可以正确释放资源。这是另一个问题,虽然:) – AlThomas

+0

所以我宣布timer_id作为一个uint和修改类像你说的,但它仍然不能按需要工作。 – Nine