what are pitfalls of making UnityContainer not thr

2019-04-29 15:38发布

I am adding dependency injection to my library and I use Unity for that. And I am wondering if I need to take some additional steps to make Unity Container thread-safe. I found couple of articles that are talking about Thread-safe container (example: http://www.fascinatedwithsoftware.com/blog/post/2012/01/04/A-Thread-Safe-Global-Unity-Container.aspx ) but I don't understand if I really need it in my project. From one side I don't want to have some nasty bugs due to race conditions from the other side I don't see in what case race condition would occur. I want to use Unity with Composition Root pattern and register all the types in the static constructor like that:

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

So, basically my question is: in what cases do I need additional thread-safe when I work with Unity DI? Where could I get race conditions or problems with thread-safety?

2条回答
趁早两清
2楼-- · 2019-04-29 16:15

Unity (and all the common containers) are guaranteed (by their designers) to be thread-safe (or at least, sort of) in case of parallel resolves with no registrations. In other words, as long as you separate the registration phase from the resolve phase, and from one point on only resolve from the container, you can call Resolve in parallel from multiple threads without problems.

As a matter of fact, as a best practice, you should always strictly separate the registration phase from the resolve phase, because this will lead to serious trouble and very hard to find race conditions.

This separation of those phases is so important, that some DI libraries (such as 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 on why Simple Injector forces you upon this model and explains what could happen in case you would be able to change the configuration. To quote part of that explanation here (but you should definitely read the whole explanation):

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.

As I see it, that 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. The writer of that article (Larry Spencer) isn't very clear, but inside his Composition Root, he creates one single Unity container and uses it for the duration of the whole application. In a sense it is still 'global', but he prevents that instance from being accessed through the application (because that is the Service Locator pattern).

Although the writer tries to create a thread-safe wrapper around the Unity container, his attempt is naive. What he does is creating a lock around every Register and Resolve method. Not only will this give enormous congestion in multi-threaded applications, it doesn't address the problems of what happens when registering and replacing instances after object graphs have already been build and cached, as both the Simple Injector documentation explains and this Unity question shows.

查看更多
我欲成王,谁敢阻挡
3楼-- · 2019-04-29 16:17

In the case of dependency injection, thread safety considerations goes a level deeper than just the Container level. The type of dependency that you register into the Container also has great significance. In most cases, the dependencies that you are registering into the Container are singletons. If your container is static then it's global to all threads. In other words, every thread will have access to the same singleton instance. Therefore, if the dependency that you are registering maintains a state (stateful), then you need to consider that other threads may change that state. To avoid this kind of headache:

1) You could limit yourself to registering dependencies that are stateless.

2) You could make a [ThreadStatic] unity instance. In which case, each thread will have its own unity instance and stateful dependencies will be less of a problem.

3) The best option is to use Unity's PerThreadLifetimeManager for stateful dependencies. Which will guarantee that each thread will have its own instance of the dependency.

查看更多
登录 后发表回答