Почему вызов Dispose () в финализаторе вызывает исключение ObjectDisposedException? - PullRequest
1 голос
/ 16 ноября 2010

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

public class NHibRepository : IDisposable
{
    public ISession Session { get; set; }
    public ITransaction Transaction { get; set; }

    // constructor
    public NHibRepository()
    {
        Session = Database.OpenSession();
    }

    public IQueryable<T> GetAll<T>()
    {
        Transaction = Session.BeginTransaction();
        return Session.Query<T>();
    }

    public void Dispose()
    {
        if (Transaction != null && Transaction.IsActive)
        {
            Transaction.Rollback(); // ObjectDisposedException on this line
        }

        Session.Close();
        Session.Dispose();
    }

    ~NHibRepository()
    {
        Dispose();
    }
}

Когда я использую репозиторий вот так, он работает нормально:

using (var repo = new NHibRepository())
{
    Console.WriteLine(repo.GetAll<Product>().Count());
}

Но когда я использую его так, он выдаст ObjectDisposedException:

var repo = new NHibRepository();
Console.WriteLine(repo.GetAll<Product>().Count());

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

Мой вопрос таков: почему утилизируется Transaction, хотя я явно не вызывал Dispose()?Я бы хотел, чтобы хранилище автоматически очищалось, если оно не было удалено явно.

Ответы [ 5 ]

6 голосов
/ 16 ноября 2010

У меня вопрос: почему Транзакция уже удалена, хотя я явно не вызывал Dispose ()?

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

Выглядит так, как будто вы неправильно внедрили шаблон одноразового использования, и это вызовет у вас горе.Читайте по шаблону и делайте это правильно;в финализаторе не должно быть утилизированных вещей, которые уже были утилизированы:

http://msdn.microsoft.com/en-us/magazine/cc163392.aspx

1 голос
/ 16 ноября 2010

потерять финализатор. Очень несколько классов в .net нуждаются в финализаторе; как правило, единственными классами .net 2.0 или новее, которые должны иметь финализаторы, являются те, чья единственная причина существования вращается вокруг него.

Если класс с финализатором содержит доступ к какому-либо другому объекту, при запуске финализатора будет применяться одно из трех условий:

  1. У другого объекта уже был запущен финализатор (если есть); избавляться от этого не нужно, так как об этом уже позаботились.
  2. У другого объекта запланирован запуск финализатора; избавляться от этого не нужно, так как об этом позаботятся автоматически.
  3. Ссылка на другой объект существует вне объекта, чей финализатор работает; это обычно означает, что не следует избавляться от него.

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

1 голос
/ 16 ноября 2010

Вы должны поставить GC.SuppressFinalize (this);в вашем методе Dispose, в противном случае финализатор удалит уже удаленный объект.Кроме того, финализаторы почти во всех случаях необходимы только для неуправляемых ресурсов

0 голосов
/ 16 ноября 2010

У вас должна быть переменная-член isDisposed, которая будет установлена ​​в true методом Dispose (). Затем в начале команды Dispose () просто вернитесь, если она уже установлена ​​в true. Согласно техническому документу .NET, Dispose () может выполняться несколько раз без побочных эффектов, и это способ сделать это.

Во-вторых, закройте класс (класс не реализует шаблон Dispose должным образом для наследования) и поместите GC.SuppressFinalize(this); прямо перед назначением переменной isDisposed в методе Dispose ().

public void Dispose()
{
   if (isDisposed) { return; }

   ....

   GC.SuppressFinalize(this);   
   isDisposed = true;
}
0 голосов
/ 16 ноября 2010

CLR не дает никаких гарантий относительно порядка вызова финализаторов. Он видит группу некорневых объектов (например, недоступных из любого корня GC) и начинает вызывать финализаторы. Неважно, что у вас есть связи в вашем графе объектов. Финализаторы могут быть вызваны в любом порядке. Финализаторы предназначены для очистки неуправляемых ресурсов, а не дочерних объектов. Вам нужно переосмыслить свой API.

...