【问题标题】:Autofac resolving a singleton creates a bottleneckAutofac 解决单例会造成瓶颈
【发布时间】:2016-01-20 01:14:30
【问题描述】:

我在一个 asp.net MVC 应用程序中使用 Autofac,遇到了锁定问题。任何时候服务依赖于单例,该服务都会从根生命周期范围内解析。这是因为 Autofac:

  • 从根范围解析单例组件
  • 从根范围解析任何具有必须从根解析的依赖项的组件。

此外,当从任何范围解析时,Autofac 会锁定该范围。我认为这些都是很好的设计决策。我的问题是依赖于单例的行为不端的类 并且 具有缓慢的构造函数。这给需要解决单例的其他人造成了瓶颈。

由于这是在 MVC 应用程序中,每个请求都被映射到构造函数注入的某个 MVC 控制器。此外,我的大多数控制器都依赖于各种单例服务(日志记录、缓存等)。

对于具有快速构造函数的东西,这不是问题。但是一旦请求了一个写得不好的类,我的吞吐量就会下降,因为每个新请求都会被那个行为不端的构造函数阻塞。例如:

鉴于这 3 个类

//registered as SingleInstance()
class MySingleton {}

class BadTransient
{
    public BadTransient(MySingleton s)
    { Thread.Sleep(5000); }
}  

class FriendlyTransient {}

已解决

using(var scope = container.BeginLifetimeScope("nested scope"))
{

    //resolves from child scope
    var myFriend = scope.Resolve<FriendlyTransient>(); 

    //resolves from root because it's a singleton
    var singleton = scope.Resolve<MySingleton>(); 

    //resolves from root because it has a singleton dependency. 
    //**** this locks the root scope for 5 seconds
    //****   no one else can resolve singletons.
    var troublemaker = scope.Resolve<BadTransient>();         
}

有没有办法避免这个瓶颈?

显而易见的答案是拥有快速的构造函数。现实情况是,并非我的代码库中的所有构造函数都可以保证很快。有很多遗留代码,有很多代码依赖于 3rd 方代码,有些代码看起来很快,但依赖于不是的代码,有些代码通常很快,但在奇怪的情况下会崩溃等等。教育开发人员只能在一定程度上起作用。

我们一直在修复构造函数,但我需要一个更主动的解决方案。让我的用户做我的 QA 是不可接受的。

注意:我不太关心不依赖单例的慢速构造函数。他们将锁定其生命周期范围,但不会阻塞其他线程

【问题讨论】:

  • 我认为 Autofac 的工作方式没有任何问题。我看到的问题在于你有一些 BadTransient 类,你在构造函数中有昂贵的逻辑。我会将构造函数中的所有逻辑移至 Initialize 方法,该方法可由 Autofac OnActivated 事件调用。所以整个初始化时间都不需要锁定作用域。
  • @nemesv 请查看我的编辑

标签: c# asp.net-mvc autofac


【解决方案1】:

我同意@nemesv 的观点,即对象构造应该很快,但这也意味着不在OnActivated 中进行初始化。相反,您应该在第一次使用组件时懒惰地执行此操作。例如通过在内部实现代理或一些Lazy&lt;T&gt;

但是,如果您的应用程序具有极高的吞吐量和并发特性,并且您通过性能分析验证锁定是一个瓶颈,您可以考虑切换到无锁 IoC 容器。

【讨论】:

  • 嗨,请查看我的编辑。我喜欢在慢速构造函数中使用惰性的想法,但我正在寻找一种更包容的解决方案——假设我无法保证所有构造函数都会很快,我如何保持我的应用程序响应?你推荐任何无锁容器吗?
  • 您当然可以将所有单例注册为瞬态。如果我没记错的话,这可以防止全局锁定。我知道的唯一(好的)无锁容器是Simple Injector(我是 Simple Injector 的首席开发人员)。一个关于 Simple Injector 的警告。它有一组非常有限的精心挑选的功能。这种设计试图引导开发人员进入成功的深渊。但由于您处理的是遗留应用程序和经验不足的开发人员,因此在应用程序中集成 Simple Injector 可能比使用 Autofac 时遇到更多麻烦。
【解决方案2】:

我通过使用 autofac 的闭包语法重新注册所有单例来解决问题。
这将构造逻辑保留在 autofac 中,但从子生命周期范围内解析单例。本质上:

builder.Register<MySingleton>().AsSelf().AsImplementedInterfaces();
//.. other registrations

var container = builder.Build();

// now resolve each singleton, forcing all to be constructed if not already
//   and then register the instance
var builder2 = new ContainerBuilder();

var mySingleton = container.Resolve<MySingleton>();
builder2.Register(c => mySingleton).AsSelf().AsImplementedInterfaces();
builder2.Update(container);

//.....
var scope = container.BeginLifetimeScope("child scope");   
scope.Resolve<MySingleton>(); //not resolved from root!

然后,由于有很多单例并且我可以通过编程方式查询它们的类型,所以我编写了一个函数,它接受一个类型列表并运行上面的代码。它必须做一些反射向导,虽然只在应用启动时在正常的 autofac 注册代码结束时运行。

void ReRegisterSingletons(IContainer container, List<Type> singletonTypes)
{
     var builder= new ContainerBuilder();
     foreach(var type in singletonTypes)
     {

          var resolved = container.Resolve(type);
          var method = this.GetType().GetMethod("ReRegister").MakeGenericMethod(new []{type});
          method.Invoke(this, new []{resolved});
     }

     builder.Update(container);
}

void Register<T>(ContainerBuilder builder, object singleton)
{
    var theObj = (T)singleton;

     //a typed lambda was the only way I could get both the class name and the interface names to resolve from the child scope. RegisterInstance still resolves from root, and the non-generic lamba register can resolve the class name from child scope but not the interface names...
    builder.Register(c => theObj).AsSelf().AsImplementedInterfaces();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-24
    • 2017-02-25
    • 2020-05-28
    相关资源
    最近更新 更多