我需要对使用singleton作用域的DI容器管理的对象使用synclock吗

本文关键字:管理 synclock 对象 作用域 singleton DI | 更新日期: 2023-09-27 18:21:36

我有以下代码:

public class DotLessFactory
{
    private LessEngine lessEngine;
    public virtual ILessEngine GetEngine()
    {
        return lessEngine ?? (lessEngine = CreateEngine());
    }
    private ILessEngine CreateEngine()
    {
        var configuration = new LessConfiguration();
        return new LessFactory().CreateEngine(configuration);
    }
}

让我们假设如下:

  1. 我总是希望返回相同的ILessEngine实例
  2. 我使用DI容器(本例中我将使用StructureMap)来管理我的对象
  3. 由于第1个假设,我的对象的寿命将是辛格尔顿
  4. CreateEngine内部的代码执行通过NuGet包引用的代码
  5. DotLess只是一个例子。此代码可适用于任何类似的NuGet包

方法1

我使用在DI容器中注册我的对象

For<DotLessFactory>().Singleton().Use<DotLessFactory>();
For<ILessEngine>().Singleton().Use(container => container.GetInstance<DotLessFactory>().GetEngine());
For<ISomeClass>().Singleton().Use<SomeClass>();

现在,我可以将ILessEngine添加到构造函数中,并让容器按照下面的代码注入它的实例。

public class SomeClass : ISomeClass
{
    private ILessEngine lessEngine;
    public SomeClass(ILessEngine lessEngine)
    {
        this.lessEngine = lessEngine;
    }
}

方法2

引入一个IDotLessFactory接口,该接口公开了GetEngine方法。我使用在DI容器中注册我的对象

For<IDotLessFactory>().Singleton().Use<DotLessFactory>();
For<ISomeClass>().Singleton().Use<SomeClass>();

现在,我的工厂将按照下面的代码创建一个ILessEngine的实例。

public class SomeClass : ISomeClass
{
    private ILessEngine lessEngine;
    public SomeClass(IDotLessFactory factory)
    {
        Factory = factory;
    }
    public IDotLessFactory Factory { get; private set; }
    public ILessEngine LessEngine
    {
        get
        {
            return lessEngine ?? (lessEngine = Factory.GetEngine());
        }
    }
}

我的问题是:

  1. 在ILessEngine方面,方法1和方法2的根本区别是什么?在方法1中,ILessEngine由容器管理,而在方法2中则不是。每种方法的优点/缺点是什么?一种方法比另一种好吗
  2. 我是否需要在CreateEngine方法中使用同步锁来确保任何方法的线程安全?在这种情况下,我什么时候应该/不应该使用synclock
  3. 我看到过在CreateEngine方法中使用Activator.CreateInstance的例子,而不是直接新建对象。是否有理由使用这种方法?这与没有在工厂对象中引入NuGet包内对象的直接依赖关系有关吗
  4. 让我们假设引用的NuGet包在后台与HttpContext一起工作。在singleton作用域中注册我的工厂会对HttpContext产生任何负面影响吗?或者这无关紧要,因为我认为NuGet包很可能管理HttpContext本身的作用域
  5. 最后,DotLessFactory最终将与Bundles(Microsoft.AspNet.Web.Optimization-NuGet包)一起使用,并且Bundle仅在ApplicationStart上实例化(不由容器管理)。Bundle将依赖于DotLessFactory的注入实例。这一事实与上述问题有什么不同吗

任何反馈都将非常有帮助。

我需要对使用singleton作用域的DI容器管理的对象使用synclock吗

具体回答这些问题并不简单,但请允许我提供一些非详尽的评论:

  1. 据我所知,没有一种方法可以保证需求#1(singleton)。这是因为两个线程可以同时执行查找,并且都将lessEngine求值为null并触发创建新实例。如果StructureMap查找是线程安全的,那么第一种方法最终可能是线程安全,但如果是这样的话,我会感到惊讶(无论如何,你不希望你的代码依赖于第三方库中的实现"细节")。

  2. 两种解决方案都犯了相同的错误,本质上是在不保护整个代码区域的情况下检查是否已经创建了实例。为了解决这个问题,引入一个私有对象变量来锁定,并保护创建实例的代码区域:

    private object engineLock = new object();
    public virtual ILessEngine GetEngine()
    {
        lock( engineLock ) { return lessEngine ?? (lessEngine = CreateEngine()); }
    }
    

    顺便说一句,如果你能让StructureMap处理整个对象链的构造,这就没有必要了,因为这将取决于StructureMap来确保根据你的容器配置的单例需求。

  3. 只有当您知道新对象有默认构造函数(例如,通过类型参数代码中的泛型约束)或您有对它们的编译时引用时,才能创建新对象。由于IoC主要创建它在编译时不知道的东西,并且在执行此操作时经常需要传递参数,因此在开头使用Activator.CreateInstance。据我所知,使用"new"生成IL来调用Activator.CreateInstance,所以最终结果是一样的。

  4. HttpContext的生存期是在应用程序之外管理的(通过ASP.NET),因此不存在作用域问题。HttpContext.Current要么被设置,要么不被设置,如果它没有被设置,那么你做的工作太早了,它不可用(或者在永远不可用的上下文中执行,例如在ASP.NET之外)

  5. 呃,不确定你在考虑什么潜在的问题,但我最好的猜测是它不会对你的代码产生任何影响。

希望这能有所帮助!