替换在Autofac中注册为singleton的一次性对象

时间:2017-12-28 07:56:58

标签: c# .net singleton autofac

我正在创建一个包含类MyService的库,该类使用第三方对象DisposableDbObject(实现IDisposable)。我公开了将其注册为单个实例的Autofac ContainerBuilder扩展(对象的创建非常昂贵)。问题是偶尔需要刷新DisposableDbObject实例(它是一个需要从文件加载新版本数据库的内存数据库的包装器)。因为,据我所知,没有安全的方法来替换SingletonInstance的组件的引用(并且ContainerBuilder.Update已过时)我将DisposableDbObjectDisposableDbObjectProvider包裹在一起类并将其注册为单身,同时可以自由地更新下面的任何内容。所以我的设置是这样的。

// DisposableDbObjectProvider.cs
public interface IDisposableDbObjectProvider  
{
    DisposableDbObject GetDb();
}

public class DisposableDbObjectProvider : IDisposableDbObjectProvider  
{
    private DisposableDbObject _obj;
    public DisposableDbObjectProvider() 
    {
        _obj = new DisposableDbObject("D:\\path\to\file");
    }
    public DisposableDbObject GetDb()
    {
         return _obj;
    }
    public void UpdateDb() 
    {
        _obj = new DisposableDbObject("D:\\path\to\new\file");
    }
}

// MyService.cs 
interface IMyService 
{
    string GetStuffFromDb();
}

class MyService 
{
    private DisposableDbObjectProvider _provider;

    class MyService(IDisposableDbObjectProvider provider) 
    {
        _provider = provider;
    }

    public string GetStuffFromDb() 
    {
        return _provider.GetDb().Read(...);
    }
}


// AutofacExtensions.cs
static class AutofacExtensions 
{
     public static ContainerBuilder WithMyService(this ContainerBuilder builder) 
     {
          builder.RegisterType<DisposableDbObjectProvider >().As<IDisposableDbObjectProvider>().SingleInstance();
          builder.RegisterType<MyService>().As<IMyService>();
     }
}

此设置至少存在三个问题。

  1. 多线程客户端应用程序(如ASP.NET WebApi2)注册MyService和一个线程(可以是ASP.NET请求处理程序),如果执行更新,它可以访问对象的两个不同版本当线程正在运行时(在我非常具体的情况下,这可能已经足够好了,虽然我宁愿避免这种情况)

  2. 替换DisposableDbObject引用后,旧引用需要Dispose调用它。现在可能有N&gt; = 1个线程来保持对该对象的引用,而当我在Dispose中调用DisposableDbObjectProvider时,这些线程可能(并且在很多情况下)会以ObjectDisposedException结束。

  3. 它破坏了客户端应该负责处理它使用的对象的规则。

  4. 我正在考虑的一种方法是将DisposableDbObjectProvider的注册更改为瞬态,DisposableDbObjectstatic字段,并在每次更新时将旧引用保存为WeakReference跟踪列表并扫描它以获取垃圾收集的引用(通过IsAlive属性)并在这些引用上调用Dispose,如下所示

    public class DisposableDbObjectProvider : IDisposableDbObjectProvider, IDisposable 
    {
        private static DisposableDbObject _obj  = new DisposableDbObject("D:\\path\to\file");
        private static List<WeakReference> _oldRefs;
        public DisposableDbObject GetDb()
        {
             return _obj;
        }
        public void UpdateDb() 
        {
             _oldRefs.Add(_obj);
             _obj = new DisposableDbObject("D:\\path\to\new\file");
        }
    
        public void Dispose() 
        {
            var deadRefs = _oldRefs.Where(x => !x.IsAlive);
            oldRefs = oldRefs.Exclude(deadRefs);
            foreach(var deadRef in deadRefs) 
            {
                ((IDisposable) deadRef.Target).Dispose();
            }
        }
    }
    

    但是,这可能只能解决问题2并且我仍然不会对这种灵魂感到非常安全(无法判断DisposableDbObjectProvider.Dispose是否会在多个线程同时调用时按预期运行。< / p>

    克服这些问题的最佳方法是什么?当然,如果有一种我更渴望听到的更好的方法,我绕过单身注册问题的解决方案可能存在缺陷。

1 个答案:

答案 0 :(得分:1)

首先,我会说我缺少线程安全性。如果对象正在使用中,则在完成所有其他线程工作之前无法更新。更新例程也是如此。在完成之前,其他线程不应该能够访问对象或其方法。这不是微不足道的,我会避免在没有深刻理解的情况下尝试实施这样的事情。好消息是有一个ReaderWriterLockSlim class旨在允许多个线程读取,但一次只能写一个线程。它还允许您在写入期间处理现有对象,不会获取读锁定,反之亦然。

public class DisposableDbObjectProvider : IDisposableDbObjectProvider, IDisposable 
{
    private DisposableDbObject _obj  = new DisposableDbObject("D:\\path\to\file");
    private ReaderWriteLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

    public DisposableDbObject AquireDb()
    {
         if(_lock.TryEnterReaderLock(100)) // how long to wait until entering fails
         {
             return _obj;
         }
         else
         {
            // unable to enter read lock in timeout
            // do something
         }
    }

    public void ReleaseDb()
    {
         // we need to exit lock after we are done with reading
         _lock.ExitReadLock();
    }

    public void UpdateDb() 
    {
         if(_lock.TryEnterWriteLock(500)) // how long to wait until entering fails
         {
            _obj.Dispose();
            _obj = new DisposableDbObject("D:\\path\to\new\file");
            _lock.ExitWriteLock(); // We need to leave write lock to let read lock to be acquired
         }
         else
         {
             // unable to enter write lock in timeout
             // do something
         }
    }

    public void Dispose() 
    {
        _obj.Dispose();
    }
}

它可能适合你,但可能你需要在整个过程中做一些调整,但这个想法很有希望清晰和有用。