Search code examples
c#dependency-injectionunity-container

Resolving dependencies in main thread vs. async tasks


I am developing an MVC-ish (possibly more like MVP) desktop application, using Unity, which has a main UI thread and periodically runs async tasks in the background. The main window's UI events are handled by MainController. When the user opens a new window, MainController will create a new child controller and pass in whatever dependencies it needs. When a timer in the main window ticks, it calls a method on MainController which starts an async task. There is task synchronization in MainController so I don't have more than one aysnc task running at a time.

I'm injecting two service facades and the async task scheduler into MainController. The two service facades have one service which they each depend on.

The facade services and task scheduler service look like this:

public class FacadeOne : IFacadeOne
{
    private readonly IFirstService firstService;
    private readonly ISecondService secondService;

    public FacadeOne(IFirstService firstService, ISecondService secondService)
    {
       this.firstService = firstService;
       this.secondService = secondService;
    }
}

public class FacadeTwo : IFacadeTwo
{
    private readonly IFirstService firstService;
    private readonly IThirdService thirdService;

    public FacadeTwo(IFirstService firstService, IThirdService thirdService)
    {
       this.firstService = firstService;
       this.thirdService = thirdService;
    }
}    

public class TaskScheduler : ITaskScheduler
{
   private readonly IFacadeOne facadeOne;
   private readonly IFacadeTwo facadeTwo;

    public TaskScheduler(IFacadeOne facadeOne, IFacadeTwo facadeTwo)
    {
       this.facadeOne = facadeOne;
       this.facadeTwo = facadeTwo;
    }
}

MainController looks like this

public class MainController
{
   private readonly IFacadeOne facadeOne;
   private readonly IFacadeTwo facadeTwo;
   private readonly ITaskScheduler taskScheduler;

   public MainController(IFacadeOne facadeOne, IFacadeTwo facadeTwo, ITaskScheduler taskScheduler)
   {
      this.facadeOne = facadeOne;
      this.facadeTwo = facadeTwo;
      this.taskScheduler = taskScheduler;
   }
}

FacadeOne and FacadeTwo both have dependencies on FirstService.

In my composition root, I create an instance of MainController. When I create that instance, I want the following to happen (here is a diagram which hopefully makes it clearer):

  • The facadeOne and facadeTwo parameters of MainController should each use the same instance of ServiceOne.
  • The facadeOne and facadeTwo parameters which are passed into the taskScheduler parameter when it is resolved should be different instances than the facadeOne and facadeTwo parameters which are passed into MainController's constructor.
  • When the taskScheduler parameter is resolved, its facadeOne and facadeTwo parameters should use the same instance of FirstService as each other, but it should be a different instance that the one used by MainController's facadeOne and facadeTwo parameters.
  • When the taskScheduler parameter is resolved, its facadeOne and facadeTwo parameters should use different instances of SecondService and ThirdService (and also FirstService as stated above) than those instances used by MainController's facadeOne and facadeTwo parameters.

Is there an easy way to do this in Unity using lifetime managers, rather than creating a convoluted and error-prone sequence of Resolves, RegisterInstances, and temporary variables?


Solution

  • I see two one ways of doing it, the second to be avoided!

    Named Registration with ContainerControlledLifetimeManager

    If you don't mind doubling some registration with named registrations, you can do that:

    container.RegisterType<IFirstService, FirstService>("MainThread", new ContainerControlledLifetimeManager());
    container.RegisterType<ISecondService, SecondService>("MainThread", new ContainerControlledLifetimeManager());
    container.RegisterType<IThirdService, ThirdService>("MainThread", new ContainerControlledLifetimeManager());
    
    container.RegisterType<IFirstService, FirstService>("TaskScheduler", new ContainerControlledLifetimeManager());
    container.RegisterType<ISecondService, SecondService>("TaskScheduler", new ContainerControlledLifetimeManager());
    container.RegisterType<IThirdService, ThirdService>("TaskScheduler", new ContainerControlledLifetimeManager());
    
    
    container.RegisterType<IFacadeOne, FacadeOne>("MainThread", new ContainerControlledLifetimeManager(),
        new InjectionConstructor(
            new ResolvedParameter<IFirstService>("MainThread"),
            new ResolvedParameter<ISecondService>("MainThread")));
    
    container.RegisterType<IFacadeTwo, FacadeTwo>("MainThread", new ContainerControlledLifetimeManager(),
        new InjectionConstructor(
            new ResolvedParameter<IFirstService>("MainThread"),
            new ResolvedParameter<IThirdService>("MainThread")));
    
    container.RegisterType<IFacadeOne, FacadeOne>("TaskScheduler", new ContainerControlledLifetimeManager(),
        new InjectionConstructor(
            new ResolvedParameter<IFirstService>("TaskScheduler"),
            new ResolvedParameter<ISecondService>("TaskScheduler")));
    
    container.RegisterType<IFacadeTwo, FacadeTwo>("TaskScheduler", new ContainerControlledLifetimeManager(),
        new InjectionConstructor(
            new ResolvedParameter<IFirstService>("TaskScheduler"),
            new ResolvedParameter<IThirdService>("TaskScheduler")));
    
    container.RegisterType<ITaskScheduler, TaskScheduler>("TaskScheduler", new ContainerControlledLifetimeManager(),
        new InjectionConstructor(
            new ResolvedParameter<IFacadeOne>("TaskScheduler"),
            new ResolvedParameter<IFacadeTwo>("TaskScheduler")));
    
    container.RegisterType<MainController>(new ContainerControlledLifetimeManager(),
        new InjectionConstructor(
            new ResolvedParameter<IFacadeOne>("MainThread"),
            new ResolvedParameter<IFacadeTwo>("MainThread"),
            new ResolvedParameter<ITaskScheduler>("TaskScheduler")));
    
    MainController imWhatYouWanted = container.Resolve<MainController>();
    

    Note1: You could remove all the "MainThread" names (not the registration, but simply make it not-named- and it would work the same. Same with the registration for ITaskScheduler itself, you could simply not name it, but you would still need to use names in it's ResolvedParameters.

    Note2: You could use HierarchicalLifetimeManager instead of ContainerControlledLifetimeManager. They behave the same if you don't use child containers.

    Hack I don't recommend

    Another way you could do it, which I don't recommend unless you can absolutely not used named registrations, is to : use HierarchicalLifetimeManager instead of ContainerControlledLifetimeManager, combined with a child container:

    container.RegisterType<IFirstService, FirstService>(new HierarchicalLifetimeManager());
    container.RegisterType<ISecondService, SecondService>(new HierarchicalLifetimeManager());
    container.RegisterType<IThirdService, ThirdService>(new HierarchicalLifetimeManager());
    
    container.RegisterType<IFacadeOne, FacadeOne>(new HierarchicalLifetimeManager(),
        new InjectionConstructor(
            new ResolvedParameter<IFirstService>(),
            new ResolvedParameter<ISecondService>()));
    
    container.RegisterType<IFacadeTwo, FacadeTwo>(new HierarchicalLifetimeManager(),
        new InjectionConstructor(
            new ResolvedParameter<IFirstService>(),
            new ResolvedParameter<IThirdService>()));
    
    IUnityContainer childContainer = container.CreateChildContainer();
    
    childContainer.RegisterType<ITaskScheduler, TaskScheduler>(new HierarchicalLifetimeManager(),
        new InjectionConstructor(
            new ResolvedParameter<IFacadeOne>(),
            new ResolvedParameter<IFacadeTwo>()));
    
    // Resolve at registration time == Bad.
    // You could do a work around too, but that's another lesson!
    ITaskScheduler taskScheduler = childContainer.Resolve<ITaskScheduler>();
    
    container.RegisterInstance<ITaskScheduler>(taskScheduler);
    
    container.RegisterType<MainController>(new HierarchicalLifetimeManager(),
        new InjectionConstructor(
            new ResolvedParameter<IFacadeOne>(),
            new ResolvedParameter<IFacadeTwo>(),
            new ResolvedParameter<ITaskScheduler>()));
    
    MainController imWhatYouShouldntWant = container.Resolve<MainController>();
    

    With that second solution, by using HierarchicalLifetimeManager, and having it resolved on the child container, Unity won't consider anything (with that LifetimeManager) when you resolve on the parent.

    Note3: Good job providing an image, it made it easy to understand what you wanted!