Рейтинговые книги
Читем онлайн Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 119 120 121 122 123 124 125 126 127 ... 407
приостановит вызывающий поток на время прохождения сборки мусора. Это очень хорошо, т.к. гарантирует невозможность обращения в коде к методам объекта, который в текущий момент уничтожается.

Методу GC.Collect() можно также предоставить числовое значение, идентифицирующее самое старое поколение, для которого будет выполняться сборка мусора. Например, чтобы проинструктировать исполняющую среду о необходимости исследования только объектов поколения 0, можно написать такой код:

...

// Исследовать только объекты поколения 0.

GC.Collect(0);

GC.WaitForPendingFinalizers();

...

Кроме того, методу Collect() можно передать во втором параметре значение перечисления GCCollectionMode для точной настройки способа, которым исполняющая среда должна принудительно инициировать сборку мусора. Ниже показаны значения, определенные этим перечислением:

public enum GCCollectionMode

{

  Default,  // Текущим стандартным значением является Forced.

  Forced,   // Указывает исполняющей среде начать сборку мусора немедленно

  Optimized // Позволяет исполняющей среде выяснить, оптимален

            // ли текущий момент для удаления объектов.

}

Как и при любой сборке мусора, в результате вызова GC.Collect() уцелевшие объекты переводятся в более высокие поколения. Модифицируйте операторы верхнего уровня следующим образом:

Console.WriteLine("***** Fun with System.GC *****");

// Вывести оценочное количество байтов, выделенных в куче.

Console.WriteLine("Estimated bytes on heap: {0}",

  GC.GetTotalMemory(false));

// Значения MaxGeneration начинаются c 0.

Console.WriteLine("This OS has {0} object generations.n",

  (GC.MaxGeneration + 1));

Car refToMyCar = new Car("Zippy", 100);

Console.WriteLine(refToMyCar.ToString());

// Вывести поколение refToMyCar.

Console.WriteLine("nGeneration of refToMyCar is: {0}",

  GC.GetGeneration(refToMyCar));

// Создать большое количество объектов для целей тестирования.

object[] tonsOfObjects = new object[50000];

for (int i = 0; i < 50000; i++)

{

  tonsOfObjects[i] = new object();

}

// Выполнить сборку мусора только для объектов поколения 0.

Console.WriteLine("Force Garbage Collection");

GC.Collect(0, GCCollectionMode.Forced);

GC.WaitForPendingFinalizers();

// Вывести поколение refToMyCar.

Console.WriteLine("Generation of refToMyCar is: {0}",

  GC.GetGeneration(refToMyCar));

// Посмотреть, существует ли еще tonsOfObjects[9000].

if (tonsOfObjects[9000] != null)

{

   Console.WriteLine("Generation of tonsOfObjects[9000] is: {0}",

                      GC.GetGeneration(tonsOfObjects[9000]));

}

else

{

  Console.WriteLine("tonsOfObjects[9000] is no longer alive.");

                  // tonsOfObjects[9000] больше не существует

}

// Вывести количество проведенных сборок мусора для разных поколений.

Console.WriteLine("nGen 0 has been swept {0} times",

  GC.CollectionCount(0));  // Количество сборок для поколения 0

Console.WriteLine("Gen 1 has been swept {0} times",

  GC.CollectionCount(1));  // Количество сборок для поколения 1

Console.WriteLine("Gen 2 has been swept {0} times",

  GC.CollectionCount(2));  // Количество сборок для поколения 2

Console.ReadLine();

Здесь в целях тестирования преднамеренно был создан большой массив типа object (состоящий из 50000 элементов). Ниже показан вывод программы:

***** Fun with System.GC *****

Estimated bytes on heap: 75760

This OS has 3 object generations.

Zippy is going 100 MPH

Generation of refToMyCar is: 0

Forcing Garbage Collection

Generation of refToMyCar is: 1

Generation of tonsOfObjects[9000] is: 1

Gen 0 has been swept 1 times

Gen 1 has been swept 0 times

Gen 2 has been swept 0 times

К настоящему моменту вы должны лучше понимать детали жизненного цикла объектов. В следующем разделе мы продолжим изучение процесса сборки мусора, обратившись к теме создания финализируемых объектов и освобождаемых объектов. Имейте в виду, что описываемые далее приемы обычно необходимы только при построении классов С#, которые поддерживают внутренние неуправляемые ресурсы.

Построение финализируемых объектов

В главе 6 вы узнали, что в самом главном базовом классе .NET Core, System.Object, определен виртуальный метод по имени Finalize(). В своей стандартной реализации он ничего не делает:

// System.Object

public class Object

{

  ...

  protected virtual void Finalize() {}

}

За счет переопределения метода Finalize() в специальных классах устанавливается специфическое место для выполнения любой логики очистки, необходимой данному типу. Учитывая, что метод Finalize() определен как защищенный, вызывать его напрямую из экземпляра класса через операцию точки нельзя. Взамен метод Finalize(), если он поддерживается, будет вызываться сборщиком мусора перед удалением объекта из памяти.

На заметку! Переопределять метод Finalize() в типах структур не разрешено. Подобное ограничение вполне логично, поскольку структуры являются типами значений, которые изначально никогда не размещаются в куче и, следовательно, никогда не подвергаются сборке мусора. Тем не менее, при создании структуры, которая содержит неуправляемые ресурсы, нуждающиеся в очистке, можно реализовать интерфейс iDisposable (вскоре он будет описан). Вспомните из главы 4, что структуры ref и структуры ref, допускающие только чтение, не могут реализовывать какой-либо интерфейс, но могут реализовывать метод Dispose().

Разумеется, вызов метода Finalize() будет происходить (в итоге) во время "естественной" сборки мусора или в случае ее принудительного запуска внутри кода с помощью GC.Collect(). В предшествующих версиях .NET (но не в .NET Core) финализатор каждого объекта вызывался при окончании работы приложения. В .NET Core нет никаких способов принудительного запуска финализатора даже при завершении приложения.

О чем бы ни говорили ваши инстинкты разработчика, подавляющее большинство классов C# не требует написания явной логики очистки или специального финализатора. Причина проста: если в классах используются лишь другие управляемые объекты, то все они в конечном итоге будут подвергнуты сборке мусора. Единственная ситуация, когда может возникнуть потребность спроектировать класс, способный выполнять после себя очистку, предусматривает работу с неуправляемыми ресурсами (такими как низкоуровневые файловые дескрипторы операционной системы, низкоуровневые неуправляемые подключения к базам данных, фрагменты неуправляемой памяти и т.д.).

В рамках платформы .NET Core неуправляемые ресурсы получаются путем прямого обращения к API-интерфейсу операционной системы с применением служб вызова платформы (Platform Invocation Services — P/Invoke) или в сложных сценариях взаимодействия с СОМ. С учетом сказанного можно сформулировать еще одно правило сборки мусора.

Правило. Единственная серьезная причина для переопределения метода Finalize() связана с использованием в классе C# неуправляемых ресурсов через P/Invoke или сложные задачи взаимодействия с СОМ (обычно посредством разнообразных членов типа System.Runtime.InteropServices.Marshal). Это объясняется тем, что в таких сценариях производится манипулирование памятью, которой исполняющая среда управлять не может.

1 ... 119 120 121 122 123 124 125 126 127 ... 407
На этой странице вы можете бесплатно читать книгу Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен бесплатно.

Оставить комментарий