autofacc解析单例会产生瓶颈

本文关键字:单例会 autofacc | 更新日期: 2023-09-27 18:07:48

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

  • 从根作用域解析单例组件
  • 解析任何来自根作用域的组件,这些组件具有必须从根解析的依赖关系。

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

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

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

给定这三个类

//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>();         
}

是否有办法可以避免这个瓶颈?

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

我们一直在修复构造器,因为我们发现他们,但我需要一个更积极主动的解决方案。让我的用户做我的QA是不可接受的。

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

autofacc解析单例会产生瓶颈

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

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

我通过使用autofacc的闭包语法重新注册所有的单例解决了这个问题。
这使构造逻辑保持自动,但从子生命周期范围解决了单例。从本质上讲:

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!

然后,因为有很多单例,而且我可以通过编程方式查询它们的类型,我编写了一个函数,它接受一个类型列表并运行上面的代码。它必须做一点反射魔法,虽然只在应用程序启动时运行在正常的自动注册代码的末尾。

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();
}