Будет ли сборщик мусора вызывать IDisposable.Dispose?

StackOverflow https://stackoverflow.com/questions/45036

  •  09-06-2019
  •  | 
  •  

Вопрос

Сеть Одноразовый шаблон подразумевает что если вы пишете финализатор и реализуете IDisposable, ваш финализатор должен явно вызывать Dispose.Это логично, и я всегда поступал именно так в тех редких ситуациях, когда требуется финализатор.

Однако что произойдет, если я просто сделаю это:

class Foo : IDisposable
{
     public void Dispose(){ CloseSomeHandle(); }
}

и не реализуйте финализатор или что-то еще.Будет ли платформа вызывать для меня метод Dispose?

Да, я понимаю, что это звучит глупо, и вся логика предполагает, что это не так, но у меня всегда были две вещи в глубине души, которые заставляли меня неуверенно.

  1. Кто-то несколько лет назад однажды сказал мне, что он действительно это сделает, и у этого человека был очень солидный послужной список «знания своего дела».

  2. Компилятор/фреймворк делает и другие «магические» вещи в зависимости от того, какие интерфейсы вы реализуете (например:foreach, методы расширения, сериализация на основе атрибутов и т. д.), поэтому вполне логично, что это тоже может быть «магией».

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

Это было полезно?

Решение

Сборщик мусора .Net вызывает метод Object.Finalize объекта при сборке мусора.К по умолчанию это делает ничего и его необходимо переопределить, если вы хотите освободить дополнительные ресурсы.

Dispose НЕ вызывается автоматически и должен быть ясность вызывается, если ресурсы должны быть освобождены, например, в блоке «использование» или «попробуйте наконец»

видеть http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx Чтобы получить больше информации

Другие советы

Я хочу подчеркнуть точку зрения Брайана в его комментарии, потому что это важно.

Финализаторы не являются детерминированными деструкторами, как в C++.Как отмечали другие, нет никакой гарантии, когда он будет вызван, и действительно, если у вас достаточно памяти, будет ли он всегда называться.

Но плохая вещь в финализаторах заключается в том, что, как сказал Брайан, они заставляют ваш объект пережить сборку мусора.Это может быть плохо.Почему?

Как вы, возможно, знаете, а может и не знаете, сборщик мусора разделен на поколения — поколение 0, 1 и 2, а также куча больших объектов.Разделение — это расплывчатый термин: вы получаете один блок памяти, но есть указатели того, где начинаются и заканчиваются объекты поколения 0.

Мысльный процесс заключается в том, что вы, скорее всего, будете использовать множество объектов, срок службы которых будет недолговечным.Таким образом, сборщику мусора должно быть легко и быстро добраться до объектов поколения 0.Поэтому, когда возникает нехватка памяти, первое, что он делает, — это коллекцию Gen 0.

Теперь, если этого недостаточно, он возвращается и выполняет проверку Gen 1 (переделывая Gen 0), а затем, если все еще недостаточно, он выполняет проверку Gen 2 (переделывая Gen 1 и Gen 0).Таким образом, очистка долгоживущих объектов может занять некоторое время и быть довольно дорогостоящей (поскольку ваши потоки могут быть приостановлены во время операции).

Это означает, что если вы сделаете что-то вроде этого:

~MyClass() { }

Ваш объект, несмотря ни на что, доживет до поколения 2.Это связано с тем, что у GC нет возможности вызвать финализатор во время сборки мусора.Таким образом, объекты, которые должны быть финализированы, перемещаются в специальную очередь для очистки другим потоком (поток финализатора, который, если вы его уничтожите, приведет к возникновению всевозможных плохих вещей).Это означает, что ваши объекты будут храниться дольше и потенциально заставят выполнять больше сборок мусора.

Итак, все это просто для того, чтобы донести до вас мысль, что вы хотите использовать IDisposable для очистки ресурсов, когда это возможно, и серьезно попытаться найти способы обойти использование финализатора.Это в интересах вашего приложения.

Здесь уже есть много хороших дискуссий, и я немного опоздал на вечеринку, но я хотел добавить несколько моментов от себя.

  • Сборщик мусора никогда не выполнит за вас метод Dispose напрямую.
  • ГК воля запускайте финализаторы, когда захотите.
  • Одним из распространенных шаблонов, используемых для объектов, имеющих финализатор, является вызов метода, который по соглашению определен как Dispose(bool dispositing), передавая false, чтобы указать, что вызов был выполнен в результате финализации, а не явного вызова Dispose.
  • Это связано с тем, что небезопасно делать какие-либо предположения о других управляемых объектах во время финализации объекта (они могут уже быть финализированы).

class SomeObject : IDisposable {
 IntPtr _SomeNativeHandle;
 FileStream _SomeFileStream;

 // Something useful here

 ~ SomeObject() {
  Dispose(false);
 }

 public void Dispose() {
  Dispose(true);
 }

 protected virtual void Dispose(bool disposing) {
  if(disposing) {
   GC.SuppressFinalize(this);
   //Because the object was explicitly disposed, there will be no need to 
   //run the finalizer.  Suppressing it reduces pressure on the GC

   //The managed reference to an IDisposable is disposed only if the 
   _SomeFileStream.Dispose();
  }

  //Regardless, clean up the native handle ourselves.  Because it is simple a member
  // of the current instance, the GC can't have done anything to it, 
  // and this is the onlyplace to safely clean up

  if(IntPtr.Zero != _SomeNativeHandle) {
   NativeMethods.CloseHandle(_SomeNativeHandle);
   _SomeNativeHandle = IntPtr.Zero;
  }
 }
}

Это самый простой вариант, но в этом шаблоне есть много нюансов, которые могут вас сбить с толку.

  • Контракт для IDisposable.Dispose указывает, что многократный вызов должен быть безопасным (вызов Dispose для уже удаленного объекта не должен ничего делать).
  • Правильное управление иерархией наследования одноразовых объектов может оказаться очень сложным, особенно если на разных уровнях появляются новые одноразовые и неуправляемые ресурсы.В приведенном выше шаблоне Dispose(bool) является виртуальным, что позволяет его переопределить и управлять им, но я считаю, что он подвержен ошибкам.

На мой взгляд, гораздо лучше полностью избегать типов, которые напрямую содержат как одноразовые ссылки, так и собственные ресурсы, которые могут потребовать финализации.SafeHandles предоставляют очень простой способ сделать это, инкапсулируя собственные ресурсы в одноразовые файлы, которые внутренне обеспечивают собственную финализацию (наряду с рядом других преимуществ, таких как удаление окна во время P/Invoke, когда собственный дескриптор может быть потерян из-за асинхронного исключения). .

Простое определение SafeHandle делает это тривиальным:


private class SomeSafeHandle
 : SafeHandleZeroOrMinusOneIsInvalid {
 public SomeSafeHandle()
  : base(true)
  { }

 protected override bool ReleaseHandle()
 { return NativeMethods.CloseHandle(handle); }
}

Позволяет упростить содержащий тип:


class SomeObject : IDisposable {
 SomeSafeHandle _SomeSafeHandle;
 FileStream _SomeFileStream;
 // Something useful here
 public virtual void Dispose() {
  _SomeSafeHandle.Dispose();
  _SomeFileStream.Dispose();
 }
}

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


РЕДАКТИРОВАТЬ:Я пошел и проверил, просто чтобы убедиться:

class Program
{
    static void Main(string[] args)
    {
        Fred f = new Fred();
        f = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Fred's gone, and he's not coming back...");
        Console.ReadLine();
    }
}

class Fred : IDisposable
{
    ~Fred()
    {
        Console.WriteLine("Being finalized");
    }

    void IDisposable.Dispose()
    {
        Console.WriteLine("Being Disposed");
    }
}

Не в случае, если вы описываете, но GC позвонит Финализатор для тебя, если он у тебя есть.

ОДНАКО.При следующей сборке мусора объект не будет собран, а попадет в очередь финализации, все будет собрано, а затем будет вызван финализатор.Следующая коллекция после этого будет освобождена.

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

Нет, не называется.

Но это позволяет легко не забыть избавиться от ваших объектов.Просто используйте using ключевое слово.

Для этого я провел следующий тест:

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo();
        foo = null;
        Console.WriteLine("foo is null");
        GC.Collect();
        Console.WriteLine("GC Called");
        Console.ReadLine();
    }
}

class Foo : IDisposable
{
    public void Dispose()
    {

        Console.WriteLine("Disposed!");
    }

ГК будет нет вызовите распоряжаться.Это может вызовите свой финализатор, но даже это не гарантируется при всех обстоятельствах.

Видеть это статья для обсуждения лучшего способа справиться с этим.

Документация по IDодноразовый дает довольно четкое и подробное объяснение поведения, а также пример кода.GC НЕ будет вызывать Dispose() метод в интерфейсе, но он вызовет финализатор вашего объекта.

Шаблон IDisposable был создан в первую очередь для вызова разработчиком. Если у вас есть объект, реализующий IDispose, разработчик должен либо реализовать using ключевое слово вокруг контекста объекта или вызовите метод Dispose напрямую.

Безопасным для шаблона является реализация финализатора, вызывающего метод Dispose().Если вы этого не сделаете, вы можете создать некоторые утечки памяти, т.е.:Если вы создаете какую-то оболочку COM и никогда не вызываете System.Runtime.Interop.Marshall.ReleaseComObject(comObject) (который будет помещен в метод Dispose).

В clr нет никакого волшебства для автоматического вызова методов Dispose, кроме отслеживания объектов, содержащих финализаторы, и сохранения их в таблице Finalizer с помощью GC и их вызова, когда GC срабатывает какая-то эвристика очистки.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top