解决主线程与异步任务中的依赖关系

Resolving dependencies in main thread vs. async tasks

我正在使用 Unity 开发 MVC-ish(可能更像 MVP)桌面应用程序,它有一个主 UI 线程并在后台定期运行异步任务。 window 的 UI 主要事件由 MainController 处理。当用户打开一个新的 window 时,MainController 将创建一个新的子控制器并传入它需要的任何依赖项。当主 window 中的计时器计时时,它会调用 MainController 上的方法来启动异步任务。 MainController中有任务同步,所以我一次不会有超过一个aysnc任务运行。

我正在向 MainController 中注入两个服务外观和异步任务调度程序。这两个服务门面有一个服务,它们各自依赖于一个服务。

外观服务和任务调度程序服务如下所示:

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 看起来像这样

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

FacadeOneFacadeTwo 都依赖于 FirstService

在我的组合根中,我创建了一个 MainController 的实例。当我创建该实例时,我希望发生以下情况 (here is a diagram which hopefully makes it clearer):

在 Unity 中是否有一种简单的方法可以使用生命周期管理器来做到这一点,而不是创建一个复杂且容易出错的 Resolves、RegisterInstances 和临时变量序列?

我看到两种一种方法,第二种要避免!

使用 ContainerControlledLifetimeManager 命名注册

如果您不介意将一些注册与命名注册加倍,您可以这样做:

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

注意 1:您可以删除所有 "MainThread" 名称(不是注册,但只需将其设为 not-named- 即可,效果相同。与 ITaskScheduler 本身的注册相同,你可以简单地不命名它,但你仍然需要在它的 ResolvedParameters.

中使用名称

注意 2:您可以使用 HierarchicalLifetimeManager 而不是 ContainerControlledLifetimeManager。如果您不使用 child 个容器,它们的行为相同。

黑客我不推荐

另一种方法是:使用 HierarchicalLifetimeManager 而不是 ContainerControlledLifetimeManager,并结合 [=51,除非您绝对不能使用命名注册,否则我不推荐这样做=]容器:

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

对于第二种解决方案,通过使用 HierarchicalLifetimeManager,并在 child 容器上解析,Unity 将不会考虑任何事情(有了那个LifetimeManager) 当你解决 parent.

注意 3:提供图片非常好,很容易理解您想要什么!

根据您的方案和图表,很明显您真正想要的是创建一个新的子容器,从中解析任务计划程序。您可以使用 ITaskScheduler 的代理 class 非常简单地完成此操作,该代理将调用转发到从子容器解析的 TaskScheduler

首先设置父容器。每当我们在子容器中解析时,使用 HierarchicalLifetimeManager 将为我们提供一个新实例:

var container = new UnityContainer();
container.RegisterType<IFirstService, FirstService>(new HierarchicalLifetimeManager());
container.RegisterType<ISecondService, SecondService>(new HierarchicalLifetimeManager());
container.RegisterType<IThirdService, ThirdService>(new HierarchicalLifetimeManager());
container.RegisterType<IFacadeOne, FacadeOne>(new HierarchicalLifetimeManager());
container.RegisterType<IFacadeTwo, FacadeTwo>(new HierarchicalLifetimeManager());

// Note that we don't map this against ITaskScheduler
container.RegisterType<TaskScheduler>(new HierarchicalLifetimeManager());

container.RegisterType<MainController>(new ContainerControlledLifetimeManager());

然后我们为代理TaskScheduler添加注册:

container.RegisterInstance<ITaskScheduler>(new UnityChildScopedTaskScheduler(container), new ContainerControlledLifetimeManager());

其中 UnityChildScopedTaskScheduler 声明为:

public class UnityChildScopedTaskScheduler : ITaskScheduler, IDisposable
{
    private IUnityContainer childContainer;

    private ITaskScheduler realTaskScheduler;
    private ITaskScheduler taskScheduler
    {
        get 
        {
            if(realTaskScheduler == null)
            {
                realTaskScheduler = childContainer.Resolve<TaskScheduler>();
            }

            return realTaskScheduler;
        }
    }

    public UnityChildScopedTaskScheduler(IUnityContainer container) 
    {
        childContainer = container.CreateChildContainer();
    }

    // Implement ITaskScheduler methods, passing the calls to taskScheduler

    public void Dispose() 
    {
        childContainer.Dispose();
    }
}

大部分代码应该是不言自明的。直接传入父容器,在第一次使用时解析真正的任务调度器。您显然可以将此通用化并应用于其他 classes。我想你也可以让容器自己注入,但我更喜欢直接注入它,这样更清楚发生了什么!

像往常一样最终从主容器解析您的控制器,您就可以开始了:

var controller = container.Resolve<MainController>();

Here's a dotnet fiddle 显示对象哈希码。