Будет ли сборщик мусора вызывать IDisposable. Утилизировать для меня? - PullRequest
122 голосов
/ 05 сентября 2008

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

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

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

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

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

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

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

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

Ответы [ 9 ]

109 голосов
/ 05 сентября 2008

.Net Garbage Collector вызывает метод Object.Finalize объекта для сборки мусора. По по умолчанию это ничего не значит и должно быть переопределено, если вы хотите освободить дополнительные ресурсы.

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

см. http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx для получения дополнительной информации

61 голосов
/ 05 сентября 2008

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

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

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

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

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

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

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

~MyClass() { }

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

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

30 голосов
/ 05 сентября 2008

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

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

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();
 }
}
6 голосов
/ 05 сентября 2008

Я так не думаю. Вы можете контролировать, когда вызывается 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");
    }
}
3 голосов
/ 05 сентября 2008

Не в том случае, если вы описываете, Но GC позвонит вам Finalizer , если он у вас есть.

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

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

1 голос
/ 05 сентября 2008

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

См. статью , где обсуждается лучший способ справиться с этим.

1 голос
/ 05 сентября 2008

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

Но это позволяет легко забыть избавиться от своих предметов. Просто используйте ключевое слово 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!");
    }
0 голосов
/ 05 сентября 2008

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

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

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

0 голосов
/ 05 сентября 2008

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

...