使 UnityContainer 不是线程安全的陷阱是什么?

what are pitfalls of making UnityContainer not thread safe?

我正在向我的库添加依赖项注入,为此我使用 Unity。 我想知道我是否需要采取一些额外的步骤来使 Unity Container 线程安全。我找到了几篇讨论线程安全容器的文章(例如: http://www.fascinatedwithsoftware.com/blog/post/2012/01/04/A-Thread-Safe-Global-Unity-Container.aspx ),但我不明白我的项目是否真的需要它。一方面,我不想因为竞争条件而出现一些讨厌的错误,另一方面,我看不到竞争条件会在什么情况下发生。我想将 Unity 与 Composition Root 模式一起使用,并像这样在静态构造函数中注册所有类型:

internal static class ConfiguredUnityContainer
    {
        private static readonly UnityContainer Container = new UnityContainer();

        static ConfiguredUnityContainer()
        {
            Container.RegisterType<IConnectionFactory<SqlConnection>>();
        }

        public static T Resolve<T>()
        {
            return Container.Resolve<T>();
        }
    }

所以,基本上我的问题是:在什么情况下使用 Unity DI 时我需要额外的线程安全?我在哪里可以获得竞争条件或线程安全问题?

Unity(以及所有通用容器)(由它们的设计者)保证在没有注册的并行解析的情况下是线程安全的(或者至少,sort of)。也就是说,只要把注册阶段和解析阶段分开,从一点开始只从容器解析,就可以多线程并行调用Resolve没有问题。

事实上,作为最佳实践,您应该始终将注册阶段与解析阶段严格分开,因为这会导致严重的麻烦并且很难找到竞争条件。

这些阶段的分离非常重要,以至于一些 DI 库(例如 Autofac and Simple Injector) force this pattern upon you (where Simple Injector is the strictest of the two). The Simple Injector documentation contains a very clear explanation 为什么 Simple Injector 会强制您使用此模型并解释如果您能够更改配置会发生什么. 在这里引用部分解释(但你一定要阅读整个解释):

Problems with thread-safety can easily emerge when the user changes a registration during a web request. If the container allowed such registration changes during a request, other requests could directly be impacted by those changes (since in general there should only be one Container instance per AppDomain). Depending on things such as the lifestyle of the registration; the use of factories and how the object graph is structured, it could be a real possibility that another request gets both the old and the new registration. Take for instance a transient registration that is replaced with a different one. If this is done while an object graph for a different thread is being resolved while the service is injected into multiple points within the graph - the graph would contain different instance of that abstraction with different lifetimes at the same time in the same request - and this is bad.

如我所见,the article you are linking goes more into the difference between the Service Locator anti-pattern and applying Dependency Injection correctly, which means only accessing the container inside your Composition Root。那篇文章的作者 (Larry Spencer) 不是很清楚,但在他的 Composition Root 中,他创建了一个单一的 Unity 容器,并在整个应用程序的持续时间内使用它。从某种意义上说,它仍然是 'global',但他阻止了通过应用程序访问该实例(因为那是服务定位器模式)。

虽然作者试图围绕 Unity 容器创建一个线程安全的包装器,但他的尝试是幼稚的。他所做的是围绕每个 RegisterResolve 方法创建一个锁。这不仅会在多线程应用程序中造成巨大的拥塞,而且不会解决在对象图已经构建和缓存后注册和替换实例时发生的问题,正如 Simple Injector 文档和这个 Unity question 显示。

在依赖注入的情况下,线程安全考虑比容器级别更深。您注册到容器中的依赖项类型也具有重要意义。在大多数情况下,您注册到容器中的依赖项是单例。如果您的容器是静态的,那么它对所有线程都是全局的。换句话说,每个线程都可以访问同一个单例实例。因此,如果您正在注册的依赖项保持状态(有状态),那么您需要考虑其他线程可能会更改该状态。为了避免这种头痛:

1) 您可以限制自己注册无状态的依赖项。

2) 你可以创建一个 [ThreadStatic] 统一实例。在这种情况下,每个线程都有自己的统一实例,并且有状态的依赖关系将不再是问题。

3) 最好的选择是使用 Unity 的 PerThreadLifetimeManager 来实现有状态的依赖。这将保证每个线程都有自己的依赖实例。