2017-01-10 155 views
1

我有一個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; 
    } 
} 

回答

1

你正在混淆自動引用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

4

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

相關問題