2017-01-10 18 views
1

У меня есть подкласс Gtk.Box, который содержит GLib.Timer, который запускает уведомление по истечении заданного интервала. У меня есть метод в этом классе, который вызывает this.destroy() в Gtk.Box. Таймер продолжает работать и запускает уведомление даже после уничтожения его родительского экземпляра. Все экземпляры этого класса, которые были уничтожены, демонстрируют это поведение и продолжают использовать процессор и память до тех пор, пока процесс не будет убит.уничтожение экземпляра класса не убивает экземпляры, которыми он владеет в 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 подсчет ссылок, что делает его «автоматический подсчет ссылок» (АРК). То есть вам не нужно беспокоиться о счете в Vala, он добавит вам соответствующие операции ref и unref.

Полная сборка мусора (как на C#, Java, ...) освобождение памяти не является детерминированной, объект может быть сохранен, даже если он больше не используется. Это делается с использованием так называемой «управляемой кучи», и сборщик мусора запускается в фоновом режиме (то есть как поток GC).

Теперь, когда у нас есть фон материал покрыт в вашу реальную проблему:

Вы должны удалить Gtk.Box от его родительского контейнера, а также задать любые ссылки вы можете все еще иметь в своем коде Вала в null в порядке чтобы получить счетчик ссылок до 0. Затем будет unref ed.

Есть, конечно, другие варианты, такие как отключение таймера и т. Д. Вам действительно нужно добавить MVCE на ваш вопрос, чтобы мы могли дать вам несколько советов по дизайну кода.

PS: подсчет ссылок часто рассматривается как простой способ сбора мусора. Вот почему я пишу полный сбор мусора (также называемый tracing garbage collection), чтобы не путать два термина. See the Wikipedia article on garbage collection.

4

GLib.Timer - секундомер, который возвращает прошедшее время. Он не генерирует события, но GLib.Timeout делает.

В GLib используется цикл событий. Это то же самое для GTK +, который использует один и тот же базовый цикл событий GLib. GLib.Timeout используется для создания одного вида источника события - таймера, который срабатывает после заданного интервала.Когда ваша программа создает источник события, вам предоставляется идентификатор источника. Например:

timer_id = Timeout.add_seconds (1, my_callback_function);

Что нужно сделать, ваша программа является магазин этот идентификатор таймера в объекте, а затем, когда вызывается обработчик нажатия кнопки вы можете удалить таймер в качестве источника событий:

Source.remove (timer_id);

Строго говоря, Вала не имеет цикла сбора мусора. Другие языки будут собирать ссылки, которые больше не используются, а затем удалять ресурсы, выделенные им во время цикла очистки. Вала использует подсчет ссылок, но он детерминирован. Поэтому, когда объект больше не используется, обычно, когда он выходит из области видимости, ресурсы, выделенные объекту, немедленно удаляются. Для обычных объектов в Vala, а не для компактных классов, деструктор также вызывается, когда объект освобождается. Это позволяет выделять ресурсы для инициализации (RAII), который будет эффективно использоваться в Вала.

В общем, вы не должны вручную освобождать объекты, подсчет ссылок Vala очень хорош. Я думаю, что важно понять цикл событий и источники событий GLib, чтобы понять, что происходит. Подробное описание см. В GLib's documentation on its main event loop.

Теперь вы предоставили MCVE, мы можем подробно рассмотреть, как Vala управляет памятью. Если вы хотите углубиться в то, что происходит за кулисами, вы можете использовать переключатель --ccode с valac.

Первая линия интереса к вашей программе является:

Timeout.add (50, update);

Глядя на код C от valac эта строка использует 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). Это увеличивает счетчик ссылок для объекта на единицу и передает указатель на объект. Это имеет большое значение, поскольку обратный вызов 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()); 

Vala для объема блока они были созданы в Назначая объект delete_me вы оставляете ссылку на объект для остальной части блока main().Изменяя это как аргумент вызова метода, он просто для вызова и поэтому освобождается при нажатии кнопки.

Кстати, GLib автоматически включается при использовании valac поэтому нет никакой необходимости в using GLib; или компиляции с --pkg glib-2.0.

+0

Я даже не осознавал, что Вала делает RAII до сих пор. Являются ли классы не GObject также выделенными в стеке (например, на C++)? –

+0

Статья Википедии относится к C++ и стек. Я думаю, что это немного вводит в заблуждение относительно общей концепции RAII. В Вале призывы освободить ресурсы создаются компилятором Vala в обратном порядке, к которому они были созданы. Таким образом, вы можете поднять «исключение» в блоке try, а деструкторы будут вызваны в обратном порядке, что позволит правильно освободить ресурсы. Это еще один вопрос, хотя :) – AlThomas

+0

поэтому я объявил timer_id как uint и модифицировал класс, как вы сказали, но он по-прежнему не работает по желанию. – Nine

 Смежные вопросы

  • Нет связанных вопросов^_^