处理比处理实例寿命更长的对象引用

Dispose of objects references that live longer than the disposing instance

我目前正在 NHibernate 之上开发一个工作单元和存储库模式(注意:我没有决定采用一种或另一种模式,因此请不要讨论存储库模式的用处关于已经实现了一个的 ORM)。 我首先根据文档构建一个创建工作单元实例的单例(使用 IoC 配置)Sessionmanager(为了便于阅读而删除了私有方法):

public sealed class SessionManager : ISessionManager
    {
        #region Members
        /// <summary>
        /// lock to manage thread safe access to the dictionary.
        /// </summary>
        private readonly ReaderWriterLockSlim _lock; 

        /// <summary>
        /// NHibernate sessionfactory to create sessions.
        /// </summary>
        private readonly ISessionFactory _factory;

        /// <summary>
        /// Units of Work that are already in use.
        /// </summary>
        private readonly Dictionary<string, IUnitOfWork> _units;

        /// <summary>
        /// Flag indicating if the manager got disposed.
        /// </summary>
        private bool _disposed;
        #endregion

        #region Constructors
        /// <summary>
        /// Creates a new Manager with the given Config.
        /// </summary>
        /// <param name="config"></param>
        public SessionManager(NHibernate.Cfg.Configuration config)
        {
            _lock = new ReaderWriterLockSlim();
            _units = new Dictionary<string, IUnitOfWork>();
            _factory = config
                .Configure()
                .AddAssembly(typeof(SessionManager).Assembly.FullName)
                .BuildSessionFactory();
            _disposed = false;
        }
        #endregion

        #region Methods
        /// <summary>
        /// Either creates or returns an existing unit of work.
        /// </summary>
        /// <param name="identifier">identifier of the uow</param>
        /// <param name="access">the data access that is needed for this uow.</param>
        /// <returns></returns>
        public IUnitOfWork Create(string identifier, DataAccess access)
        {
            Throw.IfNull(identifier, nameof(identifier));

            if (TryGetUnitOfWork(identifier, out var unit))
                return unit;

            return CreateOrReturn(identifier, access);
        }

        /// <summary>
        /// Disposes the given instance.
        /// </summary>
        /// <param name="unitOfWork">The unit of work that should get disposed.</param>
        public void DisposeUnitOfWork(IUnitOfWork unitOfWork)
        {
            Throw.IfNull(unitOfWork, nameof(unitOfWork));
            try
            {
                _lock.EnterWriteLock();
                if (_units.ContainsValue(unitOfWork))
                {
                    var key = _units.FirstOrDefault(x => x.Value.Equals(unitOfWork)).Key;
                    if (!string.IsNullOrWhiteSpace(key))
                        _units.Remove(key);
                }

                unitOfWork.Dispose();

            }
            finally
            {
                _lock.ExitWriteLock();
            }
            unitOfWork.Dispose();
        }

        /// <summary>
        /// Disposes managed and unmanaged ressources.
        /// </summary>
        /// <param name="disposing">Flag indicating if the call happened in the public <see cref="Dispose"/> method or the Finalizer.</param>
        void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {                   
                    foreach (var unit in _units)
                        unit.Value.Dispose();

                    _factory.Dispose();
                    _lock.Dispose();
                }

                // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
                // TODO: set large fields to null.

                _disposed = true;
            }
        }

        /// <summary>
        /// Disposes managed and unmanaged ressources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }
}

工作单元包含 ISessionIStatelessSession 以创建事务或所需的存储库。

public sealed class UnitOfWork : IUnitOfWork
    {
        #region Members
        private bool _disposed;

        public string Identifier { get; }

        public DataAccess Access { get; }

        private readonly ISession _session;

        private readonly IStatelessSession _statelessSession;
        #endregion

        #region Constructors
        private UnitOfWork(DataAccess access, string identifier)
        {
            Access = access;
            Identifier = identifier;
            _disposed = false;
        }

        internal UnitOfWork(DataAccess access, string identifier, ISession session)
            : this(access, identifier)
        {
            _session = session;
        }

        internal UnitOfWork(DataAccess access, string identifier, IStatelessSession session)
            : this(access, identifier)
        {
            _statelessSession = session;
        }
        #endregion

        #region Methods
        public void CloseTransaction(IGenericTransaction transaction)
        {
            transaction.Dispose();
        }


        public IGenericTransaction OpenTransaction()
        {
            if (_session != null)
                return new GenericTransaction(_session.BeginTransaction());

            if (_statelessSession != null)
                return new GenericTransaction(_statelessSession.BeginTransaction());

            throw new InvalidOperationException("You tried to create a transaction without having a vaild session.");
        }

        public IGenericTransaction OpenTransaction(IsolationLevel level)
        {
            if (_session != null)
                return new GenericTransaction(_session.BeginTransaction(level), level);

            if (_statelessSession != null)
                return new GenericTransaction(_statelessSession.BeginTransaction(level), level);

            throw new InvalidOperationException("You tried to create a transaction without having a vaild session.");
        }

        public bool Equals(IUnitOfWork other)
        {
            if (other == null)
                return false;

            return Access == other.Access;
        }

        public IRepository<T> GetRepository<T>() where T : Entity<T>
        {
            switch (Access)
            {
                case DataAccess.Statefull:
                    return new StatefullRepository<T>(_session);

                case DataAccess.Stateless:
                    return new StatelessRepository<T>(_statelessSession);

                default:
                    throw new ArgumentException("Cannot determine which repository is needed.", nameof(Access));
            }
        }

        #region IDisposable Support
        void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    if (_session != null)
                        _session.Dispose();
                    if (_statelessSession != null)
                        _statelessSession.Dispose();
                }

                // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
                // TODO: set large fields to null.

                _disposed = true;
            }
        }

        // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
        // ~UnitOfWork() {
        //   // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        //   Dispose(false);
        // }

        // This code added to correctly implement the disposable pattern.
        public void Dispose()
        {
            // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
            Dispose(true);
            // TODO: uncomment the following line if the finalizer is overridden above.
            // GC.SuppressFinalize(this);
        }
        #endregion
        #endregion
    }

正如您所见,存储库是使用 ISessionIStatelessSession 创建的。这两个接口都实现了 IDisposable 接口,这意味着它们应该被处理掉。因此,我的存储库也实现了 IDisposable。然而,问题就在这里。理论上,我可以从一个工作单元创建任意数量的存储库,例如:

public void UpdatePets(List<Cat> felines, List<Dog> carnines, ISessionManager manager)
        {
            var uow = manager.Create("Pets", DataAccess.Statefull);

            using (var t = uow.OpenTransaction())
            {
                using (var catRepo = uow.GetRepository<Cat>())
                {
                    catRepo.Add(felines);
                    t.Commit();
                }
            }

            //Some Businesslogic that forbids using one Transaction to mitigate the problem of an already disposed ISession or IStatelessSession.

            using(var t = uow.OpenTransaction())
            {
                using (var dogRepo = uow.GetRepository<Dog>())
                {
                    dogRepo.Add(carnines);
                    t.Commit();
                }
            }
        }

如果那是消耗我的工作单元的服务,我会像这样使用 "standard" IDisposable 模式:

private void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    if (_session != null)
                        _session.Dispose();
                }
                _disposed = true;
            }
        }

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

第二个存储库会抛出 ObjectDisposedexception,因为我在第二个存储库中引用的 ISession 来自我的工作单元,而当我的第一个存储库离开 using 块时,那个已经被处理掉了。因此,为了克服这个问题,我会在我的存储库中执行以下操作:

public void Dispose()
            {
                _session = null;
                GC.SuppressFinalize(this);
            }

然而,这感觉不对,因为我没有正确处理对我应该处理的对象的引用,而是 "close my Eyes and forget about it",这可能永远不是编程中的好解决方案。 所以我的问题基本上是:"Is there a good way to properly dispose objects that might live longer than the object holding the reference?"

您需要建立所有权,并使用它来确定哪个 class 负责清理每个 "thing"。它是所有者,并且只有所有者应该调用 Dispose,无论有多少其他 class 可能已经与它交互。

所有权需要 独占 并且(包装 classes 除外)嵌套1 - 所有者的生命周期需要至少与拥有的 object 一样长。通过遵循这两条规则,我们可以确保 Dispose a) 调用一次 2 b) 当不再需要 object 时调用。

存储库对 "own" session 来说是错误的。多个存储库可以被赋予相同的 session(因此,如果他们是所有者,我们就失去了独占 属性),正如您自己的标题所暗示的,他们有一个 比它短 生命周期。


一些一般经验法则

  • 作为参数传递给实例方法的 object 通常不会被实例拥有。
  • 由实例创建的 object 生命周期长于单个方法调用的生命周期的 object 将由实例拥有
  • 由实例方法创建的 object 且不长于单个方法调用的生命周期的 object 将由方法拥有

1对于包装器,内部 object 往往会首先创建,可能已配置,然后传递给包装器 object。所以包装器 object 的生命周期在内部 object.

之后开始

2不严格要求。 Disposables 应该容忍 Dispose 被多次调用,但这更多的是 belt-and-braces 防御性而不是所需的模式。