安全地重新初始化"单个实例" autofac中的依赖

时间:2015-08-30 03:22:53

标签: dependency-injection autofac

我在Web应用程序中有一个广泛使用的缓存接口,其实现当前已注册为SingleInstance

此当前缓存实现假定单线程初始化,但一旦初始化是不可变的,因此可以跨多个线程安全地共享。

但是,这意味着当前,如果基础值发生更改,则在重新启动应用程序之前,缓存不会更新。虽然更新基础值很少,但我们现在想提供修改基础值的应用程序行为,然后告诉缓存刷新。

我可以修改缓存实现以使用锁定,或者可以利用其中一个.NET并发集合来安全地更新缓存值。

但是,我想知道autofac是否提供了一种功能,允许我在下一个请求中更改新实例的已注册实例,这样就不需要修改缓存实现本身。

理想的行为是,当我们修改基础值时,我们会触发创建新的缓存实例。实例初始化完成后,所有正在进行的请求将继续使用旧的缓存实例,任何新的http请求范围都将解析为更新的实例。

autofac是否提供了支持此方案的内置方式?

2 个答案:

答案 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;