我在Web应用程序中有一个广泛使用的缓存接口,其实现当前已注册为SingleInstance
。
此当前缓存实现假定单线程初始化,但一旦初始化是不可变的,因此可以跨多个线程安全地共享。
但是,这意味着当前,如果基础值发生更改,则在重新启动应用程序之前,缓存不会更新。虽然更新基础值很少,但我们现在想提供修改基础值的应用程序行为,然后告诉缓存刷新。
我可以修改缓存实现以使用锁定,或者可以利用其中一个.NET并发集合来安全地更新缓存值。
但是,我想知道autofac是否提供了一种功能,允许我在下一个请求中更改新实例的已注册实例,这样就不需要修改缓存实现本身。
理想的行为是,当我们修改基础值时,我们会触发创建新的缓存实例。实例初始化完成后,所有正在进行的请求将继续使用旧的缓存实例,任何新的http请求范围都将解析为更新的实例。
autofac是否提供了支持此方案的内置方式?
答案 0 :(得分:4)
您永远无法安全地替换容器中的单件注册实例。一旦其他单例组件依赖于它,它们将只保留对旧实例的引用,并且替换容器中的实例意味着某些组件(将在替换操作之后创建)将引用新实例,而其他组件继续引用旧实例。这几乎不会导致你喜欢的行为,并且最有可能导致错误。
我的建议是,在应用程序运行后,永远不要尝试更改容器的注册。这将很快变得非常复杂,以监督情况是否正确并且是线程安全的。例如,如果在解析另一个线程的对象图时更换实例,该怎么办?这可能意味着该对象图同时包含对旧实例和新实例的引用。
相反,在应用程序级别解决此问题。首先,您需要两个API;一个用于读取缓存,另一个用于更新缓存。两者都可以使用相同的组件实现:
// Very simplified version of what you actually might need
interface ICache { CacheObject Get(); }
interface ICacheUpdater { void Set(CacheObject o); }
简单的实现可能如下所示:
// Using C# 6 syntax here, because it makes the example lovely short.
sealed class Cache : ICache, ICacheUpdater {
private static CacheObject instance;
public void Set(CacheObject o) => instance = o;
public CacheObject Get() => instance;
}
此实现可能有效,但如果在同一请求中多次检索缓存,则可以读取同一请求中的旧值和新值(因为不同的线程可以调用{{1介于两者之间)。这可能是个问题。在这种情况下,您可以将实现更改为以下内容:
Set
在此实现中,对缓存对象的额外引用存储在sealed class HttpCache : ICache, ICacheUpdater {
private static readonly object key = typeof(HttpCache);
private static CacheObject instance;
private static IDictionary items => HttpContext.Current.Items;
public void Set(CacheObject o) => instance = o;
public CacheObject Get() => (CacheObject)items[key] ?? (items[key] = instance);
}
字典中。这可确保在执行单个(Web)请求期间始终检索相同的实例。
此示例假定您正在运行Web应用程序,但您可以轻松想象针对不同应用程序类型的解决方案。
答案 1 :(得分:1)
要更新注册为单个实例的组件,您可以进行以下注册:
builder.RegisterType<ServiceProvider>().SingleInstance();
builder.Register(c => c.Resolve<ServiceProvider>().Service).As<IService>();
和ServiceProvider
是这样的:
public class ServiceProvider
{
public ServiceProvider()
{
this.Service = new Service();
}
public IService Service { get; set; }
}
要更新实例,您只需要这样做:
container.Resolve<ServiceProvider>().Service = newInstance;
问题的第二部分可能更难:
实例初始化完成后,所有正在进行的请求将继续使用旧的缓存实例,任何新的http请求范围都将解析为更新的实例。
您想要的是在特定范围内注入单个实例注册。为此,您可以使用ChildLifetimeScopeBeginning
事件为范围的整个生命周期设置实例。
builder.RegisterType<ServiceProvider>().Named<ServiceProvider>("root").SingleInstance();
builder.RegisterType<ServiceProvider>().InstancePerRequest();
builder.Register(c => c.Resolve<ServiceProvider>().Service).As<IService>();
IContainer container = builder.Build();
container.ChildLifetimeScopeBeginning += (sender, e) =>
{
ServiceProvider scopeServiceProvider = e.LifetimeScope.Resolve<ServiceProvider>();
ServiceProvider rootServiceProvider = container.ResolveNamed<ServiceProvider>("root");
scopeServiceProvider.Service = rootServiceProvider.Service;
};
要更改全局IService
实例,您必须解析&#34; root&#34;名为ServiceProvider
scope.ResolveNamed<ServiceProvider>("root").Service = newInstance;
并且仅更改范围IService
实例,您将解析正常的ServiceProvider
scope.Resolve<ServiceProvider>().Service = newInstance;