【问题标题】:Resolving dependencies in main thread vs. async tasks解决主线程与异步任务中的依赖关系
【发布时间】:2017-05-12 02:49:44
【问题描述】:

我正在使用 Unity 开发一个 MVC-ish(可能更像 MVP)桌面应用程序,它有一个主 UI 线程并定期在后台运行异步任务。主窗口的 UI 事件由MainController 处理。当用户打开一个新窗口时,MainController 将创建一个新的子控制器并传入它需要的任何依赖项。当主窗口中的计时器计时,它会调用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):

  • MainControllerfacadeOnefacadeTwo 参数应该使用相同的 ServiceOne 实例。
  • 解析时传递给taskScheduler 参数的facadeOnefacadeTwo 参数应该与传递给MainController 的构造函数的facadeOnefacadeTwo 参数不同。
  • 解析taskScheduler参数时,其facadeOnefacadeTwo参数应该使用相同的FirstService实例,但它应该是与MainController使用的实例不同的实例s facadeOnefacadeTwo 参数。
  • taskScheduler 参数被解析时,它的facadeOnefacadeTwo 参数应该使用与@987654360 使用的实例不同的SecondServiceThirdService 实例(还有FirstService,如上所述) @ 的 facadeOnefacadeTwo 参数。

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

【问题讨论】:

  • "...best way..." 受制于意见,可悲的是,SO 偏离主题。 How to Ask
  • 我更新了问题。
  • 我会将问题重命名为“Resolving instances with mix of TransientLifetimeManager 和 ContainerControlledLifetimeManager。”,但由于这是一个巨大的变化,如果您愿意,我会让您这样做!跨度>
  • this article 中所述,您正在创建一个“复杂对象图”。在这些情况下,DI 容器不能很好地工作。 Pure DI 是要走的路。

标签: c# dependency-injection unity-container


【解决方案1】:

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

使用 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:您可以删除所有“主线程”名称(不是注册,而是简单地使其 not-named- 并且它会工作相同。与 ITaskScheduler 本身的注册相同,你可以不命名它,但你仍然需要在它的 ResolvedParameters 中使用名称。

注意2:您可以使用HierarchicalLifetimeManager 代替ContainerControlledLifetimeManager。如果您不使用子容器,它们的行为相同。

我不推荐破解

我不推荐的另一种方法是:使用HierarchicalLifetimeManager 而不是ContainerControlledLifetimeManager,并结合子容器:

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,并在子容器上解析它,Unity 在您解析父容器时不会考虑任何事情(使用 LifetimeManager)。 p>

注意 3:提供的图片做得很好,它让你很容易理解你想要什么!

【讨论】:

  • 谢谢。我不知道使用命名实例。你的建议比我打算这样做的黑客方式要好得多。谢谢。
  • 总是很开心!
【解决方案2】:

根据您的场景和图表,您真正想要的是创建一个新的子容器来解决任务调度程序。您可以使用 ITaskScheduler 的代理类非常简单地做到这一点,该代理类将调用转发到从子容器解析的 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();
    }
}

大部分代码应该是不言自明的。只需传入父容器并在第一次使用时解析真正的任务调度程序。您显然可以将此通用化并应用于其他类。我认为您也可以让容器自行注入,但我更喜欢直接注入它,这样会更明显!

最后像往常一样从主容器中解析您的控制器,一切顺利:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-29
    相关资源
    最近更新 更多