Правильное использование интерфейса IDisposable

1402

Я знаю из чтения документации MSDN, что "основное" использование интерфейса IDisposable - очистка неуправляемых ресурсов.

Для меня "неуправляемый" означает такие вещи, как соединения с базой данных, сокеты, дескрипторы окон и т.д. Но я видел код, в котором метод Dispose() реализован для освобождения управляемых ресурсов, что кажется излишним для меня, поскольку сборщик мусора должен позаботиться об этом для вас.

Например:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

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

edit. До сих пор люди опубликовали несколько хороших примеров использования IDisposable для очистки неуправляемых ресурсов, таких как соединения с базой данных и растровые изображения. Но предположим, что _theList в приведенном выше коде содержал миллион строк, и вы хотели освободить эту память сейчас, а не ждать сборщика мусора. Может ли этот код выполнить этот код?

  • 29
    Мне нравится принятый ответ, потому что он говорит вам правильный «шаблон» использования IDisposable, но, как сказал ОП в своем редактировании, он не отвечает на заданный им вопрос. IDisposable не «вызывает» GC, он просто «помечает» объект как разрушаемый. Но каков реальный способ освободить память «прямо сейчас» вместо того, чтобы ждать, пока GC начнет действовать? Я думаю, что этот вопрос заслуживает большего обсуждения.
  • 35
    IDisposable ничего не помечает. Метод Dispose делает то, что должен, для очистки ресурсов, используемых экземпляром. Это не имеет ничего общего с GC.
Показать ещё 5 комментариев
Теги:
garbage-collection
idisposable

18 ответов

2352
Лучший ответ

Точка Dispose -, чтобы освободить неуправляемые ресурсы. Это нужно сделать в какой-то момент, иначе они никогда не будут очищены. Сборщик мусора не знает как для вызова DeleteHandle() для переменной типа IntPtr, он не знает , или не нужно вызвать DeleteHandle().

Примечание. Что такое неуправляемый ресурс? Если вы нашли его в Microsoft.NET Framework: он управляется. Если вы сами потрудились над MSDN, это неуправляемо. Все, что вы использовали P/Invoke, чтобы выйти за пределы приятного удобного мира всего, что вам доступно в .NET Framwork, неуправляемо - и теперь вы отвечаете за его очистку.

Созданный объект должен выставить метод some, который может вызвать внешний мир, чтобы очистить неуправляемые ресурсы. Метод можно назвать любым, что вам нравится:

  public void Cleanup()

public void Shutdown()
Код>

Но вместо этого существует стандартизованное имя для этого метода:

  public void Dispose()
Код>

Был создан даже интерфейс, IDisposable, который имеет только один метод:

  открытый интерфейс IDisposable
{  void Dispose()
}
Код>

Таким образом, вы создаете свой объект для интерфейса IDisposable, и таким образом вы обещаете, что вы написали этот единственный метод для очистки неуправляемых ресурсов:

  public void Dispose()
{  Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}
Код>

И все готово. Кроме того, что вы можете сделать лучше.


Что делать, если ваш объект выделил 250 МБ Система .Drawing.Bitmap (т.е. Управляемый .NET класс Bitmap) в качестве своего рода буфера кадров? Конечно, это управляемый объект .NET, и сборщик мусора освободит его. Но вы действительно хотите оставить 250MB памяти, просто сидите там, ожидая, когда сборщик мусора в в конце концов придет и освободит его? Что, если есть открытие подключения к базе данных? Конечно, мы не хотим, чтобы это соединение закрывалось, ожидая завершения GC GC.

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

Итак, теперь мы будем:

  • избавиться от неуправляемых ресурсов (потому что мы должны), и
  • избавиться от управляемых ресурсов (потому что мы хотим быть полезными)

Итак, давайте обновим наш метод Dispose(), чтобы избавиться от этих управляемых объектов:

  public void Dispose()
{  // Свободные неуправляемые ресурсы  Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
  // Свободные управляемые ресурсы тоже  if (this.databaseConnection!= null)  {     this.databaseConnection.Dispose();     this.databaseConnection = null;  }  if (this.frameBufferImage!= null)  {     this.frameBufferImage.Dispose();     this.frameBufferImage = null;  }
}
Код>

И все хорошо, за исключением того, что вы можете сделать лучше!


Что делать, если человек забыл вызвать Dispose() на вашем объекте? Затем они будут утечка некоторых неуправляемых ресурсов!

Примечание. Они не будут утечка управляемых ресурсов, потому что в конечном итоге сборщик мусора будет запускаться в фоновом потоке и освободить память, связанную с любыми неиспользуемыми объекты. Это будет включать в себя ваш объект и любые управляемые объекты, которые вы используете (например, Bitmap и DbConnection).

Если человек забыл вызвать Dispose(), мы можем еще сохранить свой бекон! У нас все еще есть способ называть его для: когда сборщик мусора наконец-то обходит освобождение (то есть завершение) нашего объекта.

Примечание. Сборщик мусора в конечном итоге освободит все управляемые объекты. Когда это произойдет, он вызывает Finalizeметод на объекте. ГК не знает, или Disposeметод. Это было просто имя, которое мы выбрали для метод, который мы называем, когда хотим избавиться от неуправляемого материала.

Разрушение нашего объекта сборщиком мусора - это perfect время, чтобы освободить эти надоедливые неуправляемые ресурсы. Мы делаем это, переопределяя метод Finalize().

Примечание.. В С# вы явно не переопределяете метод Finalize(). Вы пишете метод, который выглядит как деструктор С++, а компилятор считает, что это ваша реализация метода Finalize():

<Предварительно > <код > ~ MyObject() {   // мы завершаем (т.е. уничтожен), вызываем Dispose в случае, если пользователь забыл   Dispose();//< - Предупреждение: тонкая ошибка! Продолжай читать! } Код >

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

  public void Dispose()
{  // Свободные неуправляемые ресурсы  Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);
  // Свободные управляемые ресурсы тоже  if (this.databaseConnection!= null)  {     this.databaseConnection.Dispose();//< - crash, GC уже уничтожил его     this.databaseConnection = null;  }  if (this.frameBufferImage!= null)  {     this.frameBufferImage.Dispose();//< - crash, GC уже уничтожил его     this.frameBufferImage = null;  }
}
Код>

Итак, для того, чтобы Finalize() указать Dispose(), чтобы он не касался каких-либо управляемых ресурсов, они могут быть не там), но при этом освобождают неуправляемые ресурсы.

Стандартный шаблон для этого состоит в том, чтобы Finalize() и Dispose() вызывать метод третий (!); где вы передаете логическое высказывание, если вы вызываете его из Dispose() (в отличие от Finalize()), то есть безопасно освобождать управляемые ресурсы.

В этом внутреннем методе может задано какое-то произвольное имя, например "CoreDispose" или "MyInternalDispose", но традиционно называют его Dispose (Boolean)код>:

  protected void Dispose (Boolean disposing)
Код>

Но более полезным именем параметра может быть:

  protected void Dispose (Boolean itIsSafeToAlsoFreeManagedObjects)
{  // Свободные неуправляемые ресурсы  Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
  // Свободные управляемые ресурсы тоже, но только если меня вызывают из Dispose  // (Если я вызывается из Finalize, тогда объекты могут не существовать  // больше  if (itIsSafeToAlsoFreeManagedObjects)  {     if (this.databaseConnection!= null)     {        this.databaseConnection.Dispose();        this.databaseConnection = null;     }     if (this.frameBufferImage!= null)     {        this.frameBufferImage.Dispose();        this.frameBufferImage = null;     }  }
}
Код>

И вы измените свою реализацию метода IDisposable.Dispose() на:

  public void Dispose()
{  Dispose (истина);//Я звоню вам из Dispose, это безопасно
}
Код>

и ваш финализатор:

<Предварительно > <код > ~ MyObject() {  Dispose (ложь);//Я * не * вызываю вас из Dispose, это * не * безопасно } Код >

Примечание. Если ваш объект сходит с объекта, который реализует Dispose, то не забудьте вызвать их метод base Dispose, когда вы override Dispose:

  public Dispose()
{   пытаться   {       Dispose (истина);//true: безопасные свободные управляемые ресурсы   }   в конце концов   {       base.Dispose();   }
}
Код>

И все хорошо, за исключением того, что вы можете сделать лучше!


Если пользователь вызывает Dispose() на вашем объекте, тогда все было очищено. Позже, когда сборщик мусора появится и вызовет Finalize, он снова вызовет Dispose.

Не только это расточительно, но если у вашего объекта есть ненужные ссылки на объекты, которые вы уже удалили из вызова last на Dispose(), вы попытаетесь уничтожить их снова!

В моем коде вы заметите, что я старался удалить ссылки на объекты, которые я выбрал, поэтому я не пытаюсь вызвать Dispose в ссылке на нежелательные объекты. Но это не остановило тонкую ошибку от ползания.

Когда пользователь вызывает Dispose(): уничтожается дескриптор CursorFileBitmapIconServiceHandle. Позже, когда работает сборщик мусора, он попытается снова уничтожить тот же дескриптор.

  protected void Dispose (Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{  // Свободные неуправляемые ресурсы  Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);//< - double destroy  ...
}
Код>

Как вы это исправите, расскажите сборщику мусора, что ему не нужно беспокоиться о завершении объекта - его ресурсы уже очищены, и больше не требуется работать. Вы делаете это, вызывая GC.SuppressFinalize() в методе Dispose():

  public void Dispose()
{  Dispose (истина);//Я звоню вам из Dispose, это безопасно  GC.SuppressFinalize(это);//Привет, GC: не надо звонить позже
}
Код>

Теперь, когда пользователь вызвал Dispose(), мы имеем:

  • освобожденные неуправляемые ресурсы
  • освобожденные управляемые ресурсы

Нет смысла в GC запускать финализатор - все позаботится.

Не удалось ли использовать Finalize для очистки неуправляемых ресурсов?

Документация для Object.Finalize говорит:

Метод Finalize используется для выполнения операций очистки неуправляемых ресурсов, удерживаемых текущим объектом до уничтожения объекта.

Но в документации MSDN также говорится, что для <код > IDisposable.Disposeкод > :

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

Итак, что это? Какой из них является местом для меня, чтобы очистить неуправляемые ресурсы? Ответ:

Это ваш выбор! Но выберите Dispose.

Конечно, вы можете разместить неуправляемую очистку в финализаторе:

<Предварительно > <код > ~ MyObject() {  // Свободные неуправляемые ресурсы  Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);  // Деструктор С# автоматически вызывает деструктор своего базового класса. } Код >

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

Точное время выполнения финализатора - undefined. Чтобы обеспечить детерминированное выделение ресурсов для экземпляров вашего класса, выполните метод Закрыть или укажите IDisposable.Dispose.

Это преимущество использования Dispose для очистки неуправляемых ресурсов; вы узнаете и контролируете, когда неуправляемый ресурс очищается. Их разрушение "детерминировано" .


Чтобы ответить на ваш первоначальный вопрос: почему бы не освободить память сейчас, а не когда GC решит это сделать? У меня есть программное обеспечение для распознавания лиц, которое нуждается в, чтобы избавиться от 530 МБ внутренних изображений сейчас, так как они больше не нужны. Когда мы этого не сделаем: машина перемалывается до замены.

Чтение бонусов

Для тех, кто любит стиль этого ответа (объясняя why, поэтому how становится очевидным), я предлагаю вам прочитать главу 1 из Essential COM от Don Box:

На 35 страницах он объясняет проблемы использования двоичных объектов и изобретает COM перед вашими глазами. После того, как вы осознаете why COM, оставшиеся 300 страниц очевидны и просто детализируют реализацию Microsoft.

Я думаю, что каждый программист, который когда-либо занимался объектами или COM, должен, по крайней мере, прочитать первую главу. Это лучшее объяснение чего-либо когда-либо.

Дополнительное бонусное чтение

Когда все, что вы знаете, неправильно Эрик Липперт

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

  • 3
    Чего не хватает в вашем описании, так это определения неуправляемого ресурса.
  • 2
    За исключением того, что вы действительно хотите реализовать финализатор только при очень тщательно контролируемых обстоятельствах. У финализаторов есть проблемы с производительностью, из-за которых большинство предположений о состоянии, которые вы обычно можете сделать, являются недействительными.
Показать ещё 67 комментариев
56

IDisposable часто используется для использования оператора using и использует простой способ сделать детерминированную очистку управляемых объектов.

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}
  • 6
    Лично мне это нравится, но на самом деле это не согласуется с рекомендациями по разработке структуры.
  • 1
    Я бы посчитал это правильным дизайном, потому что он позволяет легко детерминированные области видимости и конструкции / очистки областей, особенно когда они смешаны с обработкой исключений, блокировкой и использованием блоков неуправляемых ресурсов сложными способами. Язык предлагает это как первоклассную функцию.
Показать ещё 4 комментария
34

Цель шаблона Dispose - предоставить механизм для очистки как управляемых, так и неуправляемых ресурсов, и когда это произойдет, зависит от способа вызова метода Dispose. В вашем примере использование Dispose на самом деле не делает ничего, что связано с удалением, так как очистка списка не влияет на размещение этой коллекции. Аналогично, вызовы для установки переменных в null также не влияют на GC.

Вы можете ознакомиться с этой статьей для более подробной информации о том, как реализовать шаблон Dispose, но в основном выглядит так:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Наиболее важным здесь является Dispose (bool), который фактически работает в двух разных обстоятельствах:

  • disposing == true: метод был вызван прямо или косвенно с помощью кода пользователя. Управляемые и неуправляемые ресурсы могут быть удалены.
  • disposing == false: метод был вызван средой выполнения внутри финализатора, и вы не должны ссылаться на другие объекты. Могут быть удалены только неуправляемые ресурсы.

Проблема простое, чтобы GC заботиться о том, чтобы очистить, состоит в том, что у вас нет реального контроля, когда GC будет запускать цикл сбора (вы можете вызвать GC.Collect(), но вы действительно не должны этого делать) ресурсы могут оставаться дольше, чем необходимо. Помните, что вызов Dispose() фактически не вызывает цикл сбора или каким-либо образом заставляет GC собирать/освобождать объект; он просто предоставляет средства для более детерминированной очистки используемых ресурсов и сообщает GC, что эта очистка уже выполнена.

Весь смысл IDisposable и шаблона dispose заключается не в немедленном освобождении памяти. Единственный раз, когда вызов Dispose на самом деле даже имеет шанс немедленно освободить память, когда он обрабатывает сценарий dispose == false и манипулирует неуправляемыми ресурсами. Для управляемого кода память фактически не будет восстановлена ​​до тех пор, пока GC не проведет цикл сбора, который вы действительно не контролируете (кроме вызова GC.Collect(), о котором я уже говорил, это не очень хорошая идея).

Ваш сценарий действительно недействителен, поскольку строки в .NET не используют какие-либо неизведанные ресурсы и не реализуют IDisposable, нет способа заставить их "очищаться".

  • 3
    Разве вы не забыли внедрить финализатор?
  • 0
    @Budda: Нет, он использует SafeHandle. Нет необходимости в деструкторе.
Показать ещё 2 комментария
15

Не должно быть никаких дальнейших вызовов объектных методов после вызова Dispose (хотя объект должен терпеть дальнейшие вызовы Dispose). Поэтому пример в вопросе глупо. Если Dispose вызывается, то сам объект может быть отброшен. Таким образом, пользователь должен просто отбросить все ссылки на весь этот объект (установить их в null), и все связанные с ним объекты будут автоматически очищены.

Что касается общего вопроса об управляемом/неуправляемом и обсуждении в других ответах, я думаю, что любой ответ на этот вопрос должен начинаться с определения неуправляемого ресурса.

Что сводится к тому, что есть функция, которую вы можете вызвать, чтобы привести систему в состояние, и есть еще одна функция, которую вы можете вызвать, чтобы вывести ее из этого состояния. Теперь, в типичном примере, первая может быть функцией, которая возвращает дескриптор файла, а второй - вызовом CloseHandle.

Но - и это ключ - они могут быть любой подходящей парой функций. Один строит состояние, другой срывает его. Если государство было построено, но не снесено, то существует экземпляр ресурса. Вам необходимо организовать разрывы в нужное время - ресурс не управляется CLR. Единственным автоматически управляемым типом ресурса является память. Существует два вида: GC и стек. Типы значений управляются стеком (или путем приведения в движение внутри ссылочных типов), а ссылочные типы управляются GC.

Эти функции могут приводить к изменениям состояния, которые могут свободно перемежаться или, возможно, должны быть полностью вложенными. Изменения состояния могут быть потокобезопасными, или они могут не быть.

Посмотрите на пример в вопросе "Правосудие". Изменения в отладке файла журнала должны быть полностью вложенными, или все идет не так. Также они вряд ли будут потокобезопасными.

Можно собрать поездку с сборщиком мусора, чтобы очистить неуправляемые ресурсы. Но только если функции изменения состояния являются потокобезопасными, а два состояния могут иметь периоды жизни, которые перекрываются каким-либо образом. Итак, пример юстиции ресурса НЕ должен иметь финализатор! Это никому не помогло.

Для этих ресурсов вы можете просто реализовать IDisposable без финализатора. Финализатор абсолютно необязателен - это должно быть. Это замалчивается или даже не упоминается во многих книгах.

Затем вам нужно использовать оператор using, чтобы иметь возможность гарантировать, что вызывается Dispose. Это по существу похоже на то, что вы едете со стеком (так как финализатор относится к GC, using относится к стеку).

Недопустимая часть состоит в том, что вам нужно вручную записать Dispose и заставить ее вызывать свои поля и ваш базовый класс. Программистам С++/CLI этого не нужно. В большинстве случаев компилятор записывает их для них.

Есть альтернатива, которую я предпочитаю для состояний, которые идеально встраиваются и не являются потокобезопасными (кроме всего прочего, избегая IDisposable spares, вы имеете проблему с аргументом с кем-то, кто не может удержаться от добавления финализатора для каждого класса, который реализует IDisposable).

Вместо написания класса вы пишете функцию. Функция принимает делегата для обратного вызова:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

И тогда простой пример:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

Пропускаемая лямбда служит в качестве кодового блока, поэтому, как и вы, ваша собственная структура управления служит той же цели, что и using, за исключением того, что у вас больше нет опасности, что вызывающий абонент злоупотребляет ею. Нет способа, чтобы они не смогли очистить ресурс.

Этот метод менее полезен, если ресурс является видом, который может иметь перекрывающиеся сроки жизни, потому что тогда вы хотите иметь возможность создавать ресурс A, затем ресурс B, а затем убивать ресурс A, а затем убивать ресурс B. Вы можете ' t сделать это, если вы заставили пользователя идеально вложить такие гнезда. Но тогда вам нужно использовать IDisposable (но все же без финализатора, если только вы не реализовали потоки, которые не являются бесплатными).

  • 0
    re: «После вызова метода Dispose не должно быть никаких дальнейших вызовов методов объекта». «Должен» быть оперативным словом. Если у вас ожидают асинхронные действия, они могут появиться после удаления вашего объекта. Вызывает ObjectDisposedException.
  • 0
    Ваш, похоже, единственный ответ, кроме моего, который касается идеи о том, что неуправляемые ресурсы инкапсулируют состояние, которое GC не понимает. Однако ключевым аспектом неуправляемого ресурса является то, что один или несколько объектов, состояние которых может нуждаться в очистке своего состояния, могут продолжать существовать, даже если объект, который «владеет» ресурсом, не существует. Как тебе мое определение? Довольно похоже, но я думаю, что это делает «ресурс» немного более существительным (это «согласие» внешнего объекта на изменение своего поведения в обмен на уведомление о том, когда его услуги больше не нужны)
Показать ещё 9 комментариев
14

Сценарии Я использую IDisposable: очистка неуправляемых ресурсов, отмена подписки на события, закрытые соединения

Идиома, которую я использую для реализации IDisposable (not threadsafe):

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}
  • 0
    Полное объяснение паттерна можно найти по адресу msdn.microsoft.com/en-us/library/b1yfkh5e.aspx
  • 1
    Не следует включать финализатор, если у вас нет неуправляемых ресурсов. Даже в этом случае предпочтительной реализацией является упаковка неуправляемого ресурса в SafeHandle.
11

Если MyCollection собирается собирать мусор в любом случае, вам не нужно его утилизировать. Это приведет к простому откату CPU больше, чем необходимо, и может даже аннулировать некоторый предварительно вычисленный анализ, который уже сделал сборщик мусора.

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

EDIT В ответ на комментарий Скотта:

Единственный момент, когда влияют показатели производительности GC, - это когда вызывается [sic] GC.Collect() "

Концептуально, GC поддерживает представление о графе ссылок на объекты и все ссылки на него из фреймов стека потоков. Эта куча может быть довольно большой и охватывать многие страницы памяти. В качестве оптимизации GC анализирует страницы, которые вряд ли будут меняться очень часто, чтобы избежать повторного сканирования страницы без необходимости. GC получает уведомление от ядра, когда данные на странице меняются, поэтому он знает, что страница загрязнена и требует повторного сканирования. Если коллекция находится в Gen0, то вероятно, что другие вещи на странице тоже меняются, но это менее вероятно в Gen1 и Gen2. Анекдотически эти крючки не были доступны в Mac OS X для команды, которая портировала GC на Mac, чтобы получить подключаемый модуль Silverlight, работающий на этой платформе.

Еще один момент против ненужной утилизации ресурсов: представьте ситуацию, когда процесс выгружается. Представьте также, что этот процесс работает некоторое время. Скорее всего, многие из этих страниц памяти процесса были заменены на диск. По крайней мере, они больше не находятся в кешках L1 или L2. В такой ситуации нет смысла использовать приложение для разгрузки для замены всех этих данных и кодовых страниц в память для "освобождения" ресурсов, которые в любом случае будут освобождены операционной системой, когда процесс завершится. Это относится к управляемым и даже к неуправляемым ресурсам. Только ресурсы, которые поддерживают не-фоновые потоки, должны быть удалены, иначе процесс останется в живых.

Теперь во время обычного выполнения есть эфемерные ресурсы, которые необходимо очистить правильно (поскольку @fezmonkey указывает соединения с базой данных, сокеты, дескрипторы окон), чтобы избежать неуправляемых утечек памяти. Это те вещи, которые нужно уничтожить. Если вы создаете какой-то класс, которому принадлежит поток (и по собственному я имею в виду, что он его создал, и поэтому он отвечает за то, чтобы он остановился, по крайней мере, по моему стилю кодирования), тогда этот класс, скорее всего, должен реализовать IDisposable и снести нить во время Dispose.

.NET framework использует интерфейс IDisposable как сигнал, даже предупреждающий разработчиков, что этот класс должен быть удален. Я не могу думать о каких-либо типах в рамках, реализующих IDisposable (исключая явные реализации интерфейса), где удаление необязательно.

  • 0
    Вызов Dispose совершенно действителен, законен и приветствуется. Объекты, которые реализуют IDisposable, обычно делают это по определенной причине. Единственный раз, когда на показатели производительности GC влияют, это когда вызывается GC.Collect ().
  • 0
    Для многих классов .net удаление является «несколько» необязательным, а это означает, что отказ от экземпляров «обычно» не вызовет никаких проблем, если не сходить с ума, создавая новые экземпляры и оставляя их. Например, код, сгенерированный компилятором для элементов управления, кажется, создает шрифты при создании экземпляров элементов управления и оставляет их при удалении форм; если создать и использовать тысячи элементов управления, это может связать тысячи дескрипторов GDI, но в большинстве случаев элементы управления не создаются и не уничтожаются так сильно. Тем не менее, все же следует стараться избегать такого отказа.
Показать ещё 2 комментария
10

Да, этот код является полностью избыточным и ненужным, и он не делает сборщик мусора делать что-либо, что он не сделал бы иначе (если экземпляр MyCollection выходит за пределы области видимости.) Особенно вызовы .Clear().

Отвечайте на свое редактирование: Сортировка. Если я это сделаю:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Он функционально идентичен этому для управления памятью:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Если вам действительно действительно нужно освободить память в этот самый момент, вызовите GC.Collect(). Однако здесь нет оснований делать это. При необходимости память будет освобождена.

  • 1
    Re: «Память будет освобождена, когда это необходимо.» Скорее сказать, «когда GC решит, что это необходимо». Вы можете увидеть проблемы с производительностью системы, прежде чем GC решит, что память действительно нужна. Освободить его сейчас не обязательно, но может быть полезно.
  • 1
    В некоторых угловых случаях удаление ссылок в коллекции может ускорить сборку мусора для элементов, на которые они ссылаются. Например, если большой массив создается и заполняется ссылками на новые объекты меньшего размера, но это не требуется очень долго после этого, отказ от массива может привести к тому, что эти элементы будут храниться до следующего уровня 2 GC, в то время как его обнуление первым может сделать предметы подходящими для следующего уровня 0 или уровня 1 GC. Конечно, иметь большие недолговечные объекты в куче больших объектов в любом случае нелепо (мне не нравится дизайн), но ...
Показать ещё 1 комментарий
7

Если вы хотите удалить прямо сейчас, используйте неуправляемую память.

См:

6

Я не буду повторять обычные вещи о том, как использовать или освобождать неуправляемые ресурсы, которые все были покрыты. Но я хотел бы указать, что кажется распространенным заблуждением. Учитывая следующий код

Public Class LargeStuff
  Implements IDisposable
  Private _Large as string()

  'Some strange code that means _Large now contains several million long strings.

  Public Sub Dispose() Implements IDisposable.Dispose
    _Large=Nothing
  End Sub

Я понимаю, что одноразовая реализация не соответствует текущим рекомендациям, но, надеюсь, вы все поняли. Теперь, когда вызывается Dispose, сколько памяти освобождается?

Ответ: Нет.
Вызов Dispose может освобождать неуправляемые ресурсы, он НЕ МОЖЕТ восстановить управляемую память, только GC может это сделать. Это не означает, что вышеизложенное не является хорошей идеей, следуя приведенной выше схеме, по-прежнему остается хорошей идеей. После того, как Dispose был запущен, ничего не мешает GC повторно запрашивать память, которая используется _Large, хотя экземпляр LargeStuff все еще может быть в области видимости. Строки в _Large также могут быть в гене 0, но экземпляр LargeStuff может быть gen 2, поэтому снова память будет повторно заявлена ​​раньше.
Нет смысла добавлять финализатора для вызова метода Dispose, показанного выше. Это будет просто ЗАДЕРЖАТЬ повторное требование к памяти, чтобы позволить финализатору работать.

  • 1
    Если экземпляр LargeStuff существует достаточно долго, чтобы перейти в Поколение 2, и если _Large содержит ссылку на вновь созданную строку, которая находится в Поколении 0, то если экземпляр LargeStuff без обнуления _Large , то Строка, на которую _Large будет храниться до следующей коллекции Gen2. Обнуление _Large может позволить исключить строку в следующей коллекции Gen0. В большинстве случаев обнуление ссылок бесполезно, но в некоторых случаях это может принести некоторую пользу.
5

В приведенном выше примере он по-прежнему не "освобождает память сейчас". Вся память собрана мусором, но она может позволить сбор памяти в более раннем

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

  • 0
    Посмотрите на пример @Daniel Earwicker с использованием функций более высокого порядка. Для бенчмаркинга, хронометража, логирования и т. Д. Это кажется намного проще
4

Помимо первичного использования в качестве способа управления ресурсом системных ресурсов (полностью охваченных удивительным ответом Ian, kudos!), IDisposable/using может также использоваться для области изменения состояния (критических) глобальных ресурсов: консоли, потоков, процесса, любого глобального объекта, такого как экземпляр приложения. p >

Я написал статью об этом шаблоне: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

Он иллюстрирует, как вы можете защитить некоторое часто используемое глобальное состояние в многоразовом и читаемом способе: цвета консоли, текущая культура потоков, свойства объекта приложения Excel...

4

Во всяком случае, я ожидаю, что код будет менее эффективным, чем при его отсутствии.

Вызов методов Clear() не нужен, и GC, вероятно, не сделал бы этого, если Dispose не сделал этого...

3

Есть вещи, которые операция Dispose() делает в примере кода, который может иметь эффект, который не будет происходить из-за обычного GC объекта MyCollection.

Если объекты, на которые ссылаются _theList или _theDict, ссылаются на другие объекты, тогда объект List<> или Dictionary<> не будет подлежать сбору, но не будет иметь никакого содержимого. Если бы не было операции Dispose(), как в примере, эти коллекции все равно будут содержать их содержимое.

Конечно, если бы это была ситуация, я бы назвал ее сломанным дизайном - я просто указываю (по-видимому, педантично), что операция Dispose() может быть не полностью избыточной, в зависимости от того, существуют ли другие виды использования из List<> или Dictionary<>, которые не показаны в фрагменте.

  • 0
    Это частные поля, поэтому я думаю, что будет справедливо предположить, что ОП не дает на них ссылки.
  • 0
    1) фрагмент кода является просто примером кода, поэтому я просто указываю, что может быть побочный эффект, который легко упустить из виду; 2) закрытые поля часто являются целью свойства / метода получателя - может быть, слишком много (некоторые люди считают, что получатель / установщик является своего рода анти-паттерном).
2

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

Первым примером является круговая ссылка.

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

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

Реализация IDisposable для родителей и детей - лучший способ сделать это. Когда Dispose вызывается в Parent, вызовите Dispose для всех дочерних элементов и в методе Dispose для детей установите для родительских ссылок значение null.

  • 2
    По большей части, GC не работает, идентифицируя мертвые объекты, а скорее идентифицируя живые. После того, как каждый цикл gc для каждого объекта, который был зарегистрирован для финализации, сохранен в куче больших объектов или является целью живой WeakReference , система проверит флаг, который указывает, что в последнем GC была найдена живая WeakReference ссылка цикл, и либо добавит объект в очередь объектов, нуждающихся в немедленной доработке, освободит объект из кучи больших объектов или аннулирует слабую ссылку. Циркулярные ссылки не сохранят объекты живыми, если другие ссылки не существуют.
2

Первое из определения. Для меня неуправляемый ресурс означает некоторый класс, который реализует интерфейс IDisposable или что-то созданное с использованием вызовов в dll. GC не знает, как бороться с такими объектами. Если класс имеет, например, только типы значений, то я не рассматриваю этот класс как класс с неуправляемыми ресурсами. Для моего кода я следую следующим практикам:

  • Если созданный мной класс использует некоторые неуправляемые ресурсы, значит, я также должен реализовать интерфейс IDisposable для очистки памяти.
  • Очистите объекты, как только я их закончу.
  • В моем методе dispose я перебираю все IDisposable членов класса и вызываю Dispose.
  • В моем методе метода Dispose GC.SuppressFinalize(this), чтобы уведомить сборщика мусора о том, что мой объект уже очищен. Я делаю это, потому что вызов GC - дорогостоящая операция.
  • В качестве дополнительной меры предосторожности я пытаюсь сделать возможным вызов Dispose() несколько раз.
  • Когда-то я добавляю закрытый член _disposed и проверяет вызовы методов, если объект был очищен. И если он был очищен, сгенерируйте ObjectDisposedException
    Следующий шаблон демонстрирует то, что я описал в словах как образец кода:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

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

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }
  • 1
    «Для меня неуправляемый ресурс означает некоторый класс, который реализует интерфейс IDisposable или что-то созданное с использованием вызовов в dll». Итак, вы говорите, что любой тип, который is IDisposable сам по себе должен рассматриваться как неуправляемый ресурс? Это не кажется правильным. Кроме того, если тип внедрения является чистым типом значения, вы, вероятно, предполагаете, что его не нужно удалять. Это также кажется неправильным.
  • 0
    Каждый судит сам. Я не люблю добавлять в мой код что-то просто ради дополнения. Это означает, что если я добавлю IDisposable, это означает, что я создал какую-то функциональность, которой GC не может управлять, или я полагаю, что он не сможет правильно управлять своим временем жизни.
2

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

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

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

  • 0
    Ну, ИМО, определение неуправляемого объекта понятно; любой не-GC объект .
  • 0
    @Eonil: неуправляемый объект! = Неуправляемый ресурс. Такие вещи, как события, могут быть полностью реализованы с использованием управляемых объектов, но все же представляют собой неуправляемые ресурсы, потому что - по крайней мере в случае недолговечных объектов, подписывающихся на события долгоживущих объектов - GC ничего не знает о том, как их очистить ,
2

IDisposable подходит для отмены подписки на события.

1

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

В следующем примере показан хороший пример для шаблона IDisposable с некоторым кодом и комментариями.

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню