2010-09-27 3 views
7

У меня есть несколько основных вопросов о Dispose Pattern в C#.Конкретные вопросы о C# Dispose Pattern

В следующем фрагменте кода, который, как представляется, является стандартным способом реализации шаблона удаления, вы заметите, что управляемые ресурсы не обрабатываются, если утилизация ложна. Как и когда они обрабатываются? Сможет ли GC прийти и обработать управляемые ресурсы позже? Но если это так, то что делает вызов GG.SuppressFinalize (this)? Может ли кто-нибудь дать мне пример утилизации управляемых ресурсов? Наступает развязка событий. Что-нибудь еще? Способ написания паттерна, кажется, что они будут удалены (позже), если вы ничего не сделали в разделе «если (распоряжение)». Комментарии?

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

     // There are no unmanaged resources to release, but 
     // if we add them, they need to be released here. 
    } 
    disposed = true; 

    // If it is available, make the call to the 
    // base class's Dispose(Boolean) method 
    base.Dispose(disposing); 
} 
// implements IDisposable 
public void Dispose() 
{ 
    Dispose(true); 
    GC.SuppressFinalize(this); 
} 

Верно ли то, что я читал о блокировках в Dispose (BOOL) в этой теме, How do I implement the dispose pattern in c# when wrapping an Interop COM Object?? В нем говорится: «Мета-мета-комментарий - также важно, чтобы вы никогда не приобретали блокировки или не использовали блокировку во время вашей неуправляемой очистки». Почему? Это относится и к неуправляемым ресурсам?

Наконец, делает ли когда-либо реализацию финализатора (~ MyClass() на C#) без реализации IDisposable? Я считаю, что где-то читал, что финализаторы и IDisposable не нужны (или желательно), если нет неуправляемых ресурсов. Тем не менее, я вижу использование финализатора без IDisposable в некоторых примерах (см: http://www.codeproject.com/KB/cs/idisposable.aspx как один из примеров) Спасибо, Dave

+0

Спасибо за все замечательные ответы всем! К сожалению, я могу отметить только один ответ. – Dave

ответ

5

Этого способом реализации IDisposable шаблона является отказоустойчивым образом: В случае, если клиент забывает позвонить Dispose, финализатор, вызванный временем выполнения, вызовет Dispose(false) позже (обратите внимание, что эта часть отсутствует в вашем примере).

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

Но если это так, что делает вызов GC.SuppressFinalize (this)?

Запуск финализатора осуществляется за дополнительную плату. Поэтому его следует избегать, если это возможно. Вызов GC.SuppressFinalize(this) пропустит запуск финализатора, и поэтому объект может быть собран более эффективно.

В целом, следует избегать использования финализаторов, поскольку нет гарантии, что финализатор будет работать. Некоторые из проблем, с финализаторами описываются Raymond Chen в следующем посте:

When do I need to use GC.KeepAlive?

+2

Маленькая нитьчка: * «В последнем случае ... управляемые ресурсы уже были очищены» *. Это не обязательно так: GC не детерминирован, поэтому, возможно, они будут очищены, может быть, нет. В любом случае, вы должны вести себя так, как будто они были очищены (т. Е. Вы не должны пытаться что-либо с ними делать). – LukeH

2

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

Вы не включили его в свой образец, но часто тип будет иметь деструктор, который будет вызывать Dispose(false). Таким образом, когда disposing является false, вы «знаете», что вы находитесь в финализаторе вызова, и, таким образом, должен * не * доступ к любому управляемых ресурсов, потому они могли бы уже были завершены.

Процесс финализации GC гарантирует только то, что финализаторы вызываются, не порядок, что они вызываются в.

что делает GG.SuppressFinalize (это) называют делать?

Это препятствует тому, чтобы GC добавлял ваш объект в очередь финализации и в конечном итоге вызывал object.Finalize() (т. Е. Ваш деструктор). Это оптимизация производительности, не более того.

Путь картина написана, кажется, что они получили бы утилизировать (позже), если вы не сделали ничего в разделе «если (располагающей)»

Может; это зависит от того, как этот тип написан. Важнейшим моментом для идиомы IDisposable является «детерминированная финализация» - «Я хочу, чтобы ваши ресурсы были освобождены сейчас», и это имеет значение. Если вы «игнорировать» в disposing=true блок и не «вперед» Dispose() вызова, одна из двух вещей будет:

  1. Если тип имеет финализации, финализации для объекта в конечном счете, может быть использовано где-то " позже".
  2. Если тип не имеет финализатора, управляемый ресурс «просачивается», так как Dispose() никогда не будет вызываться на них.

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

Это вопрос здравомыслия - ВАШЕ здравомыслие. Чем проще ваш код очистки, тем лучше, и это всегда хорошая идея для не исключить из Dispose(). Использование блокировок может привести к исключениям или взаимоблокировкам, которые являются хорошим способом разрушить ваш день. :-)

делает на когда-либо реализовать финализатор (~ MyClass() в C#) без реализации IDisposable

Можно, но это будет считаться плохим стилем.

1

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

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

0

Вместо того, чтобы пытаться узнать о расположении с помощью шаблона, вам может потребоваться перевернуть ситуацию и попробовать узнать, почему шаблон реализован таким образом на основе принципов CLR и предполагаемого использования интерфейса IDisposable. В этом есть очень хорошее введение, которое должно ответить на все ваши вопросы (и несколько, о которых вы не думали спросить) в http://msdn.microsoft.com/en-us/magazine/cc163392.aspx.

3

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

Когда мы избавляемся, мы хотим:

  1. Утилизируйте все одноразовые объекты члена.
  2. Утилизируйте базовый объект.
  3. Освободить неуправляемые ресурсы.

При завершении мы хотим:

  1. релиз неуправляемые ресурсы.

Добавив к этому следующие проблемы:

  1. Утилизация должна быть безопасной звонить несколько раз. Не следует ошибочно звонить x.Dispose();x.Dispose();
  2. Завершение добавляет бремя для сбора мусора. Если мы избежим этого, если можем, особенно если мы уже выпустили неуправляемые ресурсы, мы хотим подавить финализацию, поскольку она больше не нужна.
  3. Доступ к завершенным объектам чреват. Если объект завершается, то любые finalisable члены (которые также будут иметь дело с теми же проблемами, что и наш класс) могут быть или не быть уже завершены и, безусловно, будут в очереди завершения. Поскольку эти объекты, вероятно, также будут управляться одноразовыми объектами, и, поскольку их удаление освободит их неуправляемые ресурсы, мы не хотим избавляться от них в таком случае.

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

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

public class HasManagedMembers : IDisposable 
{ 
    /* more stuff here */ 
    public void Dispose() 
    { 
     //if really necessary, block multiple calls by storing a boolean, but generally this won't be needed. 
     someMember.Dispose(); /*etc.*/ 
    } 
} 

Это не имеет никакого finaliser и не нужен.

public class HasUnmanagedResource : IDisposable 
{ 
    IntPtr _someRawHandle; 
    /* real code using _someRawHandle*/ 
    private void CleanUp() 
    { 
    /* code to clean up the handle */ 
    } 
    public void Dispose() 
    { 
    CleanUp(); 
    GC.SuppressFinalize(this); 
    } 
    ~HasUnmanagedResource() 
    { 
    CleanUp(); 
    } 
} 

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

Так как SafeHandle позволяет обрабатывать второй шаблон для вас, вам это действительно не нужно. В любом случае, первый пример, который я даю, будет обрабатывать подавляющее большинство случаев, когда вам необходимо реализовать IDisposable. Шаблон, приведенный в вашем примере, должен использоваться только для обратной совместимости, например, когда вы выходите из класса, который его использует.

0

Если ваш класс будет удерживать неуправляемые ресурсы напрямую или если он может когда-либо наследоваться классом-потомком, который сделает это, шаблон утилизации Microsoft обеспечит хороший способ связать финализатор и средство удаления. Если нет реальной возможности того, что либо ваш класс, либо его потомки будут когда-либо иметь неуправляемые ресурсы напрямую, вы должны удалить код шаблона и просто реализовать Dispose напрямую. Учитывая, что Microsoft настоятельно рекомендовала, чтобы неуправляемые ресурсы были обернуты в классы, единственной целью которых является их хранение (*) (и для таких целей есть такие классы, как SafeHandle), для кода шаблона больше не нужно.

(*) Сбор мусора в .net - это многоступенчатый процесс; сначала система определяет, на какие объекты не ссылаются нигде; то он делает список объектов Finalize'able, которые нигде не упоминаются. Список и все объекты на нем будут повторно объявлены «живыми», что будет означать, что все объекты, на которые они ссылаются, также будут жить. В этот момент система выполнит фактическую сборку мусора; он затем запустит все финализаторы в списке. Если объект держится, например. прямую ссылку на ресурс шрифта (неуправляемый), а также ссылки на десять других объектов, которые, в свою очередь, содержат прямую или косвенную ссылку на еще сто объектов, а затем из-за неуправляемого ресурса объект должен получить финализатор. Когда объект приходит для сбора, ни он, ни 100+ объектов, к которым он имеет прямую или косвенную ссылку, будут иметь право на сбор до тех пор, пока пропуск после его финализатора не будет запущен.

Если вместо того, чтобы удерживать прямую ссылку на ресурс шрифта, объект содержал ссылку на объект, который содержит ресурс шрифта (и ничего больше), для последнего объекта нужен финализатор, но первый не будет (поскольку он не содержит прямого ссылки на неуправляемый ресурс. Только один объект (тот, который держал финализатор), а не 100+, должен был выжить в первой сборке мусора.

5

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

Да, реализация финализатора без реализации IDisposable не является неслыханной. Любая оболочка COM-объекта (RCW) делает это. Также класс Thread. Это было сделано, потому что просто нецелесообразно вызывать Dispose(). В случае COM-оболочки, потому что просто невозможно отслеживать все подсчеты ссылок. В случае Thread из-за необходимости присоединить() поток, чтобы вы могли вызвать Dispose(), побеждает цель иметь поток.

Обратите внимание на сообщение Джона Ханны. Реализация вашего собственного финализатора действительно ошибочна в 99,99% случаев. У вас есть классы SafeHandle для переноса неуправляемых ресурсов. Вам нужно что-то довольно неясное, чтобы они не были обернуты ими.

+0

Управляемые потоки приостановлены во время сбора мусора, но я не думаю, что они были приостановлены во время финализации. Процесс завершения работы, объекты, для которых включена финализация, и любые объекты, на которые они ссылаются прямо или косвенно, не будут собираться с мусором, но если они в противном случае имеют право на сбор мусора, они будут завершены после того, как текущая сборка мусора проход завершен. После запуска финализаторов, если объекты не перерегистрируются для завершения, они больше не будут иметь права на финализацию и будут замечены на следующем gc. – supercat

+0

@super - вы правы, не знаете, почему я это написал. Ущерб отменен, спасибо. –

+0

@Hans: Я думаю, что класс Thread использует финализаторы для очистки распределения управляемых идентификаторов потоков. Каждый отдельно созданный объект Thread должен иметь отдельный идентификатор; потому что идентификаторы потоков - всего 32 бита, фреймворк должен иметь возможность их повторно использовать (иначе любая программа, которая создала два миллиарда объектов потока в течение своего срока службы, умрет). Так как плохие вещи могут произойти, если два объекта Thread имеют один и тот же ManagedThreadID, финализаторы используются для обеспечения того, чтобы идентификаторы были доступны только для повторного использования, когда больше нет ссылок на объекты Thread, которые их хранят. – supercat

Смежные вопросы