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

问题描述 投票:0回答:1

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

// 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> = 1个线程来保持对该对象的引用,而当我在Dispose中调用DisposableDbObjectProvider时,这些线程可能(并且在许多情况下)将以ObjectDisposedException结束。
  3. 它打破了客户应该负责处理它使用的对象的规则。

我正在考虑的一种方法是将DisposableDbObjectProvider的注册更改为瞬态,使用DisposableDbObject作为static字段,并在每次更新时将旧引用保存为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是否会在几个线程同时调用时按预期运行。

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

c# .net singleton autofac
1个回答
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();
    }
}

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

© www.soinside.com 2019 - 2024. All rights reserved.