Как мне убедиться, что Winform собирает мусор?

Как я узнал из Интернета и моего личного эксперимента, финализатор формы (System.Windows.Forms.Form) никогда не вызывается GC. Говорят, что внутри Dispose () формы вызывается GC.SuppressFinalize (), чтобы финализатор больше не вызывался.

Пример:

public partial class UpdateForm : Form
{
    public UpdateForm()
    {
        InitializeComponent();

        // Listen to the event of some model
        Database.OnDataUpdated += new EventHandler(DataBase_OnDataUpdated);
    }

    ~UpdateForm()
    {
        // Never gets called.
    }

    private void DataBase_OnDataUpdated(object sender, EventArgs e)
    {
        // Update data on this form
    }
}

Однако, как показано в приведенном выше примере, если форма соединяет (+ =) событие некоторой модели и не отключает (- =) событие в Dispose (), форма никогда не будет собираться мусором, даже если Dispose ( ) называется.

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

 int[] dummyArray = new int[1024 * 1024 * 128]; // Comsume 128MB memory

Затем я смотрю на профиль памяти диспетчера задач в Windows, чтобы увидеть, уменьшается ли использование памяти, когда я вызываю GC.Collect () после удаления формы.

Мой метод неумный, и мне интересно, есть ли другой более умный способ или какие-то инструменты, чтобы подтвердить, что форма на самом деле собрана мусором? Спасибо.


person Paul20    schedule 14.07.2013    source источник
comment
Форма по-прежнему собирает мусор, даже если финализатор не вызывается. Но зачем вам вызывать финализатор? Что вы могли бы добавить в финализатор, чего не не должны вставлять в метод Dispose?   -  person    schedule 14.07.2013
comment
Извините за запутанность. Я не имел в виду, что хочу, чтобы вызывали финализатор. Я просто хочу убедиться, что форма собрана мусором, т.е. вся память, которую использует форма, должна быть освобождена. Я обычно использую финализатор, чтобы проверить, действительно ли объект собран сборщиком мусора, чего нельзя сделать с помощью формы.   -  person Paul20    schedule 14.07.2013


Ответы (2)


Вы можете сохранить слабую ссылку на свою форму, используя класс WeakReference. :

var weakref = new WeakReference(form);

Слабая ссылка не препятствует сборке объекта сборщиком мусора, и вы можете использовать это свойство, чтобы проверить, был ли он:

if (weakref.IsAlive) { /* not yet garbage collected */ }

Для работы формы не требуется финализатор.

person Community    schedule 14.07.2013
comment
Спасибо. Это то, что я искал: общий метод определения того, жив ли объект (не обязательно форма). Теперь мне интересно, что использование формы в качестве примера может ввести в заблуждение. - person Paul20; 15.07.2013
comment
Я провел несколько экспериментов, используя WeakReference, как указано выше. Я заметил, что форма НЕ была собрана в мусор, когда на нее нет другой ссылки, даже после вызова GC.Collect (). Я предполагаю, что WeakReference решает, можно ли собрать форму сборщиком мусора. Следовательно, нет гарантии, что форма будет удалена сборщиком мусора сразу после вызова GC.Collect (). - person Paul20; 16.07.2013
comment
@ Paul20 Нет, WeakReference никогда не предотвращает сборку мусора для объекта (формы). Если вы можете привести небольшой пример, который достоверно показывает, что форма не собирается сборщиком мусора, хотя, по вашему мнению, так и должно быть, я буду рад взглянуть. - person ; 16.07.2013
comment
хорошо, это старый вопрос, кстати, для будущих читателей, убедитесь, что ваша форма не имеет делегатов для других форм / элементов управления, которые все еще живы, потому что, когда вы регистрируетесь в другом событии формы / элемента управления, издатель (элемент управления, который предоставляет событие) содержат ссылку на вызываемого, поэтому в вашей форме все еще есть ссылка, которая поддерживает его жизнь и не подлежит сбору. Чтобы решить эти проблемы, я предлагаю использовать EventAggregator и отображать / потреблять события с помощью этого - person Not Important; 13.12.2015

    Database.OnDataUpdated += new EventHandler(DataBase_OnDataUpdated);

Да, это проблема. Общая диагностика заключается в том, что объект-источник событий переживает объект прослушивателя событий. Или, другими словами, ваш объект формы продолжает прослушивать обновления базы данных даже после того, как он был закрыт пользователем. Обычно это вызывает исключение, наиболее типичный способ обнаружения проблемы, шаблонное исключение ObjectDisposedException, когда обработчик событий пытается обновить удаленные элементы управления. Не совсем ясно, как вам удалось избежать этого режима отказа, убедитесь, что вы не перевели этот режим отказа, скажем, с помощью попытки / улова.

И да, это вызывает проблему с сборщиком мусора. Объект базы данных имеет ссылку на ваш объект формы. Вы дали ему эту ссылку, когда подписались на мероприятие. И использует его снова, позже, когда запускает событие. Необходимо, потому что ваш метод DataBase_OnDataUpdated () является методом экземпляра вашего класса. Сахар синтаксиса C # скрывает этот факт. Фактический код под этим простым оператором назначения события выглядит следующим образом (недействительный код C #):

var delegateObject = new EventHandler(this, &DataBase_OnDataUpdated);
Database.OnDataUpdated = Delegate.Combine(DataBase_OnDataUpdated, delegateObject);

Это скрытый this в вызове конструктора делегата, который передает ссылку на ваш объект формы объекту базы данных. Что хранит его в поле Delegate.Target. Будет использоваться позже, когда он запускает событие.

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

Есть и другие шаблоны, позволяющие избежать этой проблемы. Вы можете, например, передать ссылку на свою форму классу базы данных, который может сохранить ее в списке активных форм, которые прослушивают уведомления. Он может подписаться на событие Disposed формы, чтобы знать, что форма мертва, и удалить объект из этого списка. Вам также необходимо, чтобы ваша форма реализовывала интерфейс, методы, которые класс базы данных вызывает, когда происходит что-то интересное. Антипод паттерна наблюдателя, в остальном не такой красивый, как использование событий.

Или просто решите проблему, поскольку теперь вы знаете, что ее вызывает. Просто отмените подписку на событие явно:

    protected override void OnFormClosed(FormClosedEventArgs e) {
        Database.OnDataUpdated -= DataBase_OnDataUpdated;
        base.OnFormClosed(e);
    }

Обратите внимание на то, что точно такой же код требуется, когда вы подписываетесь на другие источники событий в .NET, которые переживают своих слушателей. Как события, вызванные SystemEvents и Application.Idle

person Hans Passant    schedule 14.07.2013
comment
Из того, как я прочитал вопрос, OP уже знает, где отменить подписку на событие, и просто ищет способ подтвердить, что это действительно решает проблему (что никакие другие ссылки не препятствуют сборке мусора). Фактически, отказ от подписки в Dispose (), как упоминается в OP, вероятно, лучше, чем в OnFormClosed (), поскольку Dispose () обычно вызывается, даже если перед отображением формы возникает исключение. Вы дали (очень) хорошо написанный ответ, но я думаю, что это ответ на несколько иной вопрос. - person ; 14.07.2013
comment
@Hans: Большое спасибо за подробный и полезный ответ. Я действительно узнал из него несколько новых вещей. Однако ответ hvd ближе к тому, что я пытался найти. Извините, что я не могу четко объяснить свой вопрос. - person Paul20; 15.07.2013