解决主线程与异步任务中的依赖关系
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;
}
}
FacadeOne
和 FacadeTwo
都依赖于 FirstService
。
在我的组合根中,我创建了一个 MainController
的实例。当我创建该实例时,我希望发生以下情况 (here is a diagram which hopefully makes it clearer):
MainController
的 facadeOne
和 facadeTwo
参数应分别使用 ServiceOne
的相同实例。
- 解析时传入
taskScheduler
参数的facadeOne
和facadeTwo
参数应该与facadeOne
和facadeTwo
参数不同传递给 MainController
的构造函数的参数。
- 解析
taskScheduler
参数时,其facadeOne
和facadeTwo
参数应该使用相同的FirstService
实例,但应该是不同的MainController
的 facadeOne
和 facadeTwo
参数使用的实例。
- 解析
taskScheduler
参数时,其facadeOne
和facadeTwo
参数应使用SecondService
和ThirdService
的不同实例(以及FirstService
如上所述)比 MainController
的 facadeOne
和 facadeTwo
参数使用的那些实例。
在 Unity 中是否有一种简单的方法可以使用生命周期管理器来做到这一点,而不是创建一个复杂且容易出错的 Resolve
s、RegisterInstance
s 和临时变量序列?
我看到两种一种方法,第二种要避免!
使用 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 显示对象哈希码。
我正在使用 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;
}
}
FacadeOne
和 FacadeTwo
都依赖于 FirstService
。
在我的组合根中,我创建了一个 MainController
的实例。当我创建该实例时,我希望发生以下情况 (here is a diagram which hopefully makes it clearer):
MainController
的facadeOne
和facadeTwo
参数应分别使用ServiceOne
的相同实例。- 解析时传入
taskScheduler
参数的facadeOne
和facadeTwo
参数应该与facadeOne
和facadeTwo
参数不同传递给MainController
的构造函数的参数。 - 解析
taskScheduler
参数时,其facadeOne
和facadeTwo
参数应该使用相同的FirstService
实例,但应该是不同的MainController
的facadeOne
和facadeTwo
参数使用的实例。 - 解析
taskScheduler
参数时,其facadeOne
和facadeTwo
参数应使用SecondService
和ThirdService
的不同实例(以及FirstService
如上所述)比MainController
的facadeOne
和facadeTwo
参数使用的那些实例。
在 Unity 中是否有一种简单的方法可以使用生命周期管理器来做到这一点,而不是创建一个复杂且容易出错的 Resolve
s、RegisterInstance
s 和临时变量序列?
我看到两种一种方法,第二种要避免!
使用 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 显示对象哈希码。