почему Destructor не вызывается для этого кода - PullRequest
0 голосов
/ 09 июня 2018

Я прочитал эту статью и эту тоже.

Я пытался реализовать деструктор в простом коде.

class Program
{
    static void Main(string[] args)
    {
        CreateSubscriber();
        Console.Read();
    }

    static void CreateSubscriber()
    {
        Subscriber s = new Subscriber();
        s.list.Clear();
    }
}

public class Subscriber
{
    public List<string> list = new List<string>();
    public Subscriber()
    {
        for(long i = 0; i < 1000000; i++)
        {
            list.Add(i.ToString());
        }
    }

    ~Subscriber()
    {
        //this line is only performed on explicit GC.Collect()
        Console.WriteLine("Destructor Called - Sub");
    }
}

Когда код достиг строки Console.Read(), экземпляр Subscriber больше не находился в области видимости (я ожидал, что он будет иметь право на сборку мусора).Я оставил приведенный выше код в течение почти 2 часов в ожидании destructor из Subscriber.но это никогда не вызывало, ни память, занятая освобожденным кодом.

Я понял, в c # мы не можем назвать деструкторы программными, и он автоматически вызывается на Garbage collection, поэтому я попытался вызвать GC.Collect() явно.

Делая это, я мог видеть, что деструктор был вызван.Так что в моем коде выше сборка мусора не производилась!Но почему?

Это потому, что программа однопоточная и этот поток ожидает ввода данных пользователем на Console.Read()?

Или у него есть что-то со списком строк?если да, то что это


Обновление (для будущих читателей)

, как Фабьян предложил в своем ответе

Скорее всего где-токогда создается новый объект и для него выделяется память, GC выполняет проверку всех ссылок и собирает первый объект.

и предлагает попробовать

CreateSubscriber();
Console.Read();
CreateSubscriber();
Console.Readkey();

Iобновил код, как показано ниже,

class Program
{
    static void Main(string[] args)
    {
        CreateSubscriber(true);
        Console.ReadLine();
        CreateSubscriber(false);
        Console.ReadLine();
    }
    static void CreateSubscriber(bool toDo)
    {
        Subscriber s = new Subscriber(toDo);
        s.list.Clear();
    }
}
public class Subscriber
{
    public List<string> list = new List<string>();
    public Subscriber(bool toDo)
    {
        Console.WriteLine("In Consutructor");
        if (toDo)
        {
            for (long i = 0; i < 5000000; i++)
                list.Add(i.ToString());
        }
        else
        {
            for (long i = 0; i < 2000000; i++)
                list.Add(i.ToString());
        }
        Console.WriteLine("Out Consutructor");
    }
    ~Subscriber()
    {
        Console.WriteLine("Destructor Called - Sub");
    }
}

вывод:

enter image description here

и, как он ожидал, при создании второго экземпляра Subscriber, я мог видеть сбор GC (вызывался финализатор).

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

там в другом состоянии, я мог бы оставить список строк пустым (поэтому использование памяти будет значительно уменьшено).Но при этом GC не собирается.Скорее всего, из-за причины, упомянутой М.Аруси в комментарии к вопросу.

в дополнение к тому, что сказано выше, сборщик мусора будет собирать данные только после того, как поколение заполнится (или из-за явного вызова)и только 1 созданный объект не будет вызывать его.Да, объект пригоден для завершения, но у GC нет причин его собирать.

1 Ответ

0 голосов
/ 09 июня 2018

Когда код достиг строки Console.Read (), экземпляр Subscriber больше не находился в области (я ожидал, что он будет иметь право на сборку мусора).

Когда GCобнаруживает, что ссылка для экземпляра Subscriber потеряна (находится вне области видимости), он помечает этот объект для сбора в одном из следующих раундов.Но только GC знает , когда точно будет в следующем раунде.

Это потому, что программа однопоточная, и этот поток ожидает ввода пользователя в Console.Read ()?

Нет, если мы запустим этот код вВ отдельном потоке результат будет одинаковым.Однако, если мы изменим это:

CreateSubscriber();
Console.Read();

На:

CreateSubscriber();
Console.Read();
CreateSubscriber();
Console.Readkey();

Мы могли видеть, что GC будет собирать мусор и запускать финализатор после Console.Read().Почему?

Скорее всего, где-то, когда создается новый объект и для него выделяется память, GC выполняет проверку всех ссылок и собирает первый объект.

Давайте подведем некоторые итоги:

  • Когда мы только создаем объект и в коде нет ссылки на этот объект или его класс до завершения программы - GC позволяет программе завершать работу и собирать мусор перед выходом.

  • Когда мы создаем объект и существует какая-то ссылка на obj или его класс - GC выполняет проверку и собирает мусор.

Существует некоторая сложная логика того, как и когда GC выполняет сбор и как и когда заканчивается срок действия объекта.

Цитата из ответа Эрика Липперта :

Время жизни может быть увеличено с помощью различных механизмов, включая захват внешних переменных лямбда-выражением, блоков итераторов, асинхронных методов и т. Д.

Это оченьредко когда нам нужно выполнить какой-то код для уничтожения объекта .В этом конкретном сценарии вместо угадывания , когда объект будет уничтожен, мы можем явно запустить GC.Collect.

Чаще, хотя нам может потребоваться освободить некоторые управляемые ресурсы и для этого мы могли бы использоватьИнтерфейс IDisposable и оператор using, которые будут автоматически вызывать Dispose до того, как поток управления покинет блок кода (он создаст предложение try {} finally {} и, наконец, вызовет для нас Dispose).

using(myDisposable) 
{ 
   ... 
}  // dispose is called here
...