我正在创建一个包含类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>();
}
}
此设置至少存在三个问题。
多线程客户端应用程序(如ASP.NET WebApi2)注册MyService
和一个线程(可以是ASP.NET请求处理程序),如果执行更新,它可以访问对象的两个不同版本当线程正在运行时(在我非常具体的情况下,这可能已经足够好了,虽然我宁愿避免这种情况)
替换DisposableDbObject
引用后,旧引用需要Dispose
调用它。现在可能有N&gt; = 1个线程来保持对该对象的引用,而当我在Dispose
中调用DisposableDbObjectProvider
时,这些线程可能(并且在很多情况下)会以ObjectDisposedException
结束。
它破坏了客户端应该负责处理它使用的对象的规则。
我正在考虑的一种方法是将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
是否会在多个线程同时调用时按预期运行。< / p>
克服这些问题的最佳方法是什么?当然,如果有一种我更渴望听到的更好的方法,我绕过单身注册问题的解决方案可能存在缺陷。
答案 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();
}
}
它可能适合你,但可能你需要在整个过程中做一些调整,但这个想法很有希望清晰和有用。