Search code examples
c#inversion-of-controlninject

How to use dependencies with a class used in a Parallel.ForEach loop, while practicing Dependency Injection / IoC


I have some code that looks like below. I'm new to Inversion of Control and Ninject and don't know how to inject IOtherClass into SomeService so that the Parallel.ForEach works. My only other guess is to move the Parallel.ForEach into the DoWork method?

Thanks for any help.

public class SomeService
{
    Parallel.Foreach(items, item => new OtherClass.DoWork(item);
}

public class OtherClass:IOtherClass
{
    public void DoWork()
    {...}
}

Solution

  • When applying dependency injection, you like to move the control of the composition of graphs of related object to a single place in the application called the Composition Root.

    To remove complexity in the rest of the application, only the composition root should know which instances to create and how to create them. The composition root is the only one to know how object graphs are built and it knows (or at least when writing your DI configuration you should know) if services can safely depend on each other. Problems with thread-safety should be spotted here.

    An implementation itself should not have to know whether it is safe to use the dependency; it must be able to assume that that dependency is safe to use on the thread it is running. Besides the usual transient and singleton lifestyles (where transient means that a new instance is created where singleton means that the application will only have one instance of that service) there are often other lifestyles that have a thread affinity. Take for instance the Per Web Request lifestyles.

    So the general advice is to:

    • Let the IoC container resolve all objects for you, and
    • Don't move services from thread to thread.

    Your SomeService implementation is correct from a thread-safety perspective, since each thread creates its own new (transient) OtherClass instance. But this starts to become problematic when OtherClass starts to have dependencies on its own. In that case you should move the creation of that object to your composition root. However, if you implement that as in the following example, you will have a problem:

    public class SomeService
    {
        private IOtherClass other;
        public SomeService(IOtherClass other)
        {
            this.other = other;
        }
    
        public void Operate(Items items)
        {   
            Parallel.Foreach(items, item => this.other.DoWork(item));
        }
    }
    

    The implementation is problematic, because SomeService moves a single IOtherClass instance across threads and assumes that IOtherClass is thread-safe, which is knowledge that only the composition root should know. Dispersing this knowledge throughout the application increases complexity.

    When dealing with multi-threaded code, every thread should get its own object graph. This means that when starting a new thread, you should query the container/kernel again for a root object and call that object. Like this:

    public class SomeService
    {
        private IItemProcessor processor;
        
        public SomeService(IItemProcessor processor) => this.processor = processor;
    
        public void Operate(Items items) => this.processor.Process(items);
    }
    
    // Due to the dependency on IKernel, this class should be part of
    // your Composition Root.
    public class ItemProcessor : IItemProcessor
    {
        private IKernel container;
        
        public ItemProcessor(IKernel container) => this.container = container;
        
        public void Process(Items items)
        {
            Parallel.Foreach(items, item =>
            {
                // request a new IOtherClass again on each thread.          
                var other = this.container.Get<IOtherClass>();
                other.DoWork(item);
            });    
        }
    }
    

    This deliberately extracts the mechanics about processing those items into a different class. This allows keeping the business logic in the SomeService and allows to move the ItemProcessor (which should only contain mechanics) into the composition root so the use of the Service Locator anti-pattern is prevented.

    This article explains more about working with DI in multi-threaded applications. Note that it's documentation for a different framework, but the general advice is the same.