替换在Autofac中注册为singleton的一次性对象
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了替换在Autofac中注册为singleton的一次性对象相关的知识,希望对你有一定的参考价值。
我正在创建一个包含类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 ofile");
}
public DisposableDbObject GetDb()
{
return _obj;
}
public void UpdateDb()
{
_obj = new DisposableDbObject("D:\path o
ewfile");
}
}
// 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> = 1个线程来保持对该对象的引用,而当我在Dispose
中调用DisposableDbObjectProvider
时,这些线程可能(并且在许多情况下)将以ObjectDisposedException
结束。 - 它打破了客户应该负责处理它使用的对象的规则。
我正在考虑的一种方法是将DisposableDbObjectProvider
的注册更改为瞬态,使用DisposableDbObject
作为static
字段,并在每次更新时将旧引用保存为WeakReference
跟踪列表并扫描它以获取垃圾收集的引用(通过IsAlive
属性)并在那些上调用Dispose
,如下
public class DisposableDbObjectProvider : IDisposableDbObjectProvider, IDisposable
{
private static DisposableDbObject _obj = new DisposableDbObject("D:\path ofile");
private static List<WeakReference> _oldRefs;
public DisposableDbObject GetDb()
{
return _obj;
}
public void UpdateDb()
{
_oldRefs.Add(_obj);
_obj = new DisposableDbObject("D:\path o
ewfile");
}
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
是否会在几个线程同时调用时按预期运行。
什么是克服这些问题的最佳方法?当然,如果我有一个更好的方法来解决它,我绕过单身注册问题的解决方案可能存在缺陷。
首先,我会说我缺少线程安全性。如果对象正在使用中,则在完成所有其他线程工作之前无法更新。更新例程也是如此。在完成之前,其他线程不应该能够访问对象或其方法。这不是微不足道的,我会避免在没有深刻理解的情况下尝试实施这样的事情。好消息是有一个ReaderWriterLockSlim class旨在允许多个线程读取,但一次只能写一个线程。它还允许您在写入期间处理现有对象,不会获取读取锁定,反之亦然。
public class DisposableDbObjectProvider : IDisposableDbObjectProvider, IDisposable
{
private DisposableDbObject _obj = new DisposableDbObject("D:\path ofile");
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 o
ewfile");
_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();
}
}
它可能对你有用,但可能你需要在整个过程中做一些调整,但这个想法很有希望清晰和有用。
以上是关于替换在Autofac中注册为singleton的一次性对象的主要内容,如果未能解决你的问题,请参考以下文章
[Asp.Net Core]Autofac整合.NET5 MVC
[Asp.Net Core]Autofac整合.NET5 MVC