混合生活方式每个线程和每个Web请求与简单的喷油器 [英] Mixed lifestyle for Per Thread and Per Web Request with Simple Injector
问题描述
我用 SimpleInjector
我的IoC的图书馆。我注册的DbContext
按Web请求,它工作正常。但是,我在后台线程中运行一个任务。所以,我有一个问题,创建的DbContext
实例。例如。
-
服务1
的实例的DbContext
-
客服2
的实例的DbContext
-
服务1
和客服2
运行。 -
服务1
获取一个实体,并把它传递给客服2
-
客服2
使用该实体,而是实体从分离的DbContext
从后台线程
其实,问题就在这里: Service1.DbContext
是差 Service2.DbContext
看来当我运行在ASP.NET MVC一个单独的线程任务, SimpleInjector
创建的DbContext $ C的新实例$ C>每个呼叫。尽管一些国际奥委会库(例如
StructureMap
)具有每线程每WebRequest的一个混合的生活方式,似乎 SimpleInjector
有没有之一。我说得对不对?
你有什么想法解决 SimpleInjector
这个问题?
先谢谢了。
编辑:
我的服务在这里:
类服务:IService1 {
公共服务1(MyDbContext上下文){}
}一流的客服2:IService2 {
公共服务2(MyDbContext上下文,IService1服务1){}
}类SyncServiceUsage {
公共SyncServiceUsage(服务2服务2){
//从HttpContext.Current使用服务2(和服务1和的DbContext)
}
}类AsyncServiceUsage {
公共AsyncServiceUsage(服务2服务2){
//从后台线程使用服务2(和服务1和的DbContext)
}
}公共类AsyncCommandHandlerDecorator< TCommand>
:ICommandHandler< TCommand>其中,TCommand:ICommand的{ 私人只读Func键< ICommandHandler< TCommand>> _厂; 公共AsyncCommandHandlerDecorator(Func键< ICommandHandler< TCommand>>厂){
_factory =厂;
} 公共无效手柄(TCommand命令){
ThreadPool.QueueUserWorkItem(_ => {
//在这个线程创建新处理程序。
无功处理= _factory();
handler.Handle(命令);
});
}
}无效InitializeSimpleInjector(){
注册AsyncCommandHandlerDecorator的服务(实际上是命令)与异步启动
}
我的用户客服2
有时和 AsyncService2
其他时间。
看来,当我在ASP.NET MVC一个独立的线程运行任务,
SimpleInjector每个调用创建的DbContext的新实例。
块引用>简单的喷油器V1.5的
RegisterPerWebRequest
生活方式及以下的行为是当实例请求的Web请求的上下文外面返回一个瞬时对象(其中HttpContext.Current
为空)。返回一个瞬态的实例在简单的喷油器设计缺陷,因为这很容易隐藏不当使用。 简单注射器将抛出一个异常,而不是返回错误的一个短暂的1.6版例如,要沟通清楚,你有错误配置的容器中。
尽管一些国际奥委会库(例如StructureMap)有一个混合
生活方式的每个线程每WebRequest的的,它看似简单喷油器
有没有一
块引用>这是正确的,简单的喷油器没有内置在,因为几个原因混生活方式的支持。首先,这是一个非常奇特的功能,不是很多人需要的。其次,你可以任意两种或三种生活方式混合在一起,所以这将是混合动力车的几乎无限的组合。而在去年,这是(pretty)易做这个自己注册。
虽然你可以混合每个Web请求,提供的每个线程的生活方式,它可能会更好,当你混合每个Web请求用的每终身范围,因为与终身范围您明确开始和结束的范围(和可以处置
的DbContext
当范围结束)。简单的注射器2 而上,你可以轻松地搭配任何数量生活方式一起使用 Lifestyle.CreateHybrid 方法。下面是一个例子:
VAR hybridLifestyle = Lifestyle.CreateHybrid(
()=> HttpContext.Current!= NULL,
新WebRequestLifestyle(),
新LifetimeScopeLifestyle());//注册混合动力PerWebRequest / PerLifetimeScope。
container.Register&LT;的DbContext,MyDbContext&GT;(hybridLifestyle);有另外一个问题,#2是进入这一问题的深一点,你可能想看看:<一href=\"https://stackoverflow.com/questions/11041601/simple-injector-multi-threading-in-mvc3-asp-net\">Simple喷油器:多线程在ASP.NET MVC3
更新
关于您的更新。你几乎没有。即在后台线程运行的命令需要一个的终身范围内运行的的,所以你必须明确地启动它。这里的窍门就是调用
BeginLifetimeScope
在新的线程,但实际的命令处理程序(及其附属)之前创建。换句话说,要做到这一点的最好办法是一个装饰中。最简单的解决方案是更新
AsyncCommandHandlerDecorator
添加范围:公共类AsyncCommandHandlerDecorator&LT; TCommand&GT;
:ICommandHandler&LT; TCommand&GT;其中,TCommand:ICommand的
{
私人只读集装箱_container;
私人只读Func键&LT; ICommandHandler&LT; TCommand&GT;&GT; _厂; 公共AsyncCommandHandlerDecorator(集装箱货柜,
FUNC&LT; ICommandHandler&LT; TCommand&GT;&GT;厂)
{
_container =容器;
_factory =厂;
} 公共无效手柄(TCommand命令)
{
ThreadPool.QueueUserWorkItem(_ =&GT;
{
使用(_container.BeginLifetimeScope())
{
//在这个线程创建新处理器
//和寿命范围内。
无功处理= _factory();
handler.Handle(命令);
}
});
}
}这主张 SOLID 原则会喊这个类是纯粹主义者违反单一职责原则,因为这两种装饰上运行一个新的线程指令,并启动一个新的生命周期范围。我也不太担心这一点,因为我认为有启动后台线程并启动了一生的范围(你不会使用一个没有其他反正)之间的密切关系。但尽管如此,你可以很容易地将
AsyncCommandHandlerDecorator
不变,并创建一个新的LifetimeScopedCommandHandlerDecorator
如下:公共类LifetimeScopedCommandHandlerDecorator&LT; TCommand&GT;
:ICommandHandler&LT; TCommand&GT;其中,TCommand:ICommand的
{
私人只读集装箱_container;
私人只读Func键&LT; ICommandHandler&LT; TCommand&GT;&GT; _厂; 公共LifetimeScopedCommandHandlerDecorator(集装箱货柜,
FUNC&LT; ICommandHandler&LT; TCommand&GT;&GT;厂)
{
_container =容器;
_factory =厂;
} 公共无效手柄(TCommand命令)
{
使用(_container.BeginLifetimeScope())
{
//处理程序必须的寿命范围内创建。
无功处理= _factory();
handler.Handle(命令);
}
}
}在这些装饰是注册的顺序当然是必不可少的,因为
AsyncCommandHandlerDecorator
必须包裹LifetimeScopedCommandHandlerDecorator
。这意味着LifetimeScopedCommandHandlerDecorator
注册必须先来:container.RegisterDecorator(typeof运算(ICommandHandler&LT;&GT;)
typeof运算(LifetimeScopedCommandHandlerDecorator&LT;&GT;)
backgroundCommandCondition);container.RegisterDecorator(typeof运算(ICommandHandler&LT;&GT;)
typeof运算(AsyncCommandHandlerDecorator&LT;&GT;)
backgroundCommandCondition);<一个href=\"https://stackoverflow.com/questions/10304023/simpleinjector-is-this-the-right-way-to-registermanyforopengeneric-when-i-have\">This老问题#1这个更详细会谈。你一定要来看看。
I'm using
SimpleInjector
as my IoC library. I registerDbContext
as per web request and it works fine. But there is one task that I run it in a background thread. So, I have a problem to createDbContext
instances. e.g.
Service1
has an instance ofDbContext
Service2
has an instance ofDbContext
Service1
andService2
run from background thread.Service1
fetches an entity and pass it toService2
Service2
uses that entity, but entity is detached fromDbContext
Actually the problem is here:
Service1.DbContext
is difference fromService2.DbContext
.It seems when I run a task in a separate thread in ASP.NET MVC,
SimpleInjector
creates a new instance ofDbContext
for each call. While some IoC libraries (for exampleStructureMap
) have a mixed lifestyle for per-thread-per-webrequest, it seemsSimpleInjector
hasn't one. Am I right?Have you any idea to solve this problem in
SimpleInjector
? Thanks in advance.EDIT:
My services are here:
class Service1 : IService1 { public Service1(MyDbContext context) { } } class Service2 : IService2 { public Service2(MyDbContext context, IService1 service1) { } } class SyncServiceUsage { public SyncServiceUsage(Service2 service2) { // use Service2 (and Service1 and DbContext) from HttpContext.Current } } class AsyncServiceUsage { public AsyncServiceUsage(Service2 service2) { // use Service2 (and Service1 and DbContext) from background thread } } public class AsyncCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> where TCommand : ICommand { private readonly Func<ICommandHandler<TCommand>> _factory; public AsyncCommandHandlerDecorator(Func<ICommandHandler<TCommand>> factory) { _factory = factory; } public void Handle(TCommand command) { ThreadPool.QueueUserWorkItem(_ => { // Create new handler in this thread. var handler = _factory(); handler.Handle(command); }); } } void InitializeSimpleInjector() { register AsyncCommandHandlerDecorator for services (commands actually) that starts with "Async" }
I user
Service2
sometimes andAsyncService2
other times.解决方案It seems when I run a task in a separate thread in ASP.NET MVC, SimpleInjector creates a new instance of DbContext for each call.
The behavior of the
RegisterPerWebRequest
lifestyle of Simple Injector v1.5 and below is to return a transient instance when instances are requested outside the context of a web request (whereHttpContext.Current
is null). Returning a transient instance was a design flaw in Simple Injector, since this makes it easy to hide improper usage. Version 1.6 of the Simple Injector will throw an exception instead of incorrectly returning a transient instance, to communicate clearly that you have mis-configured the container.While some IoC libraries (for example StructureMap) have a mixed lifestyle for per-thread-per-webrequest, it seems Simple Injector hasn't one
It is correct that Simple Injector has no built-in support for mixed lifestyles because of a couple reasons. First of all it's quite an exotic feature that not many people need. Second, you can mix any two or three lifestyles together, so that would be almost an endless combination of hybrids. And last, it is (pretty) easy do register this yourself.
Although you can mix Per Web Request with Per Thread lifestyles, it would probably be better when you mix Per Web Request with Per Lifetime Scope, since with the Lifetime Scope you explicitly start and finish the scope (and can dispose the
DbContext
when the scope ends).From Simple Injector 2 and on, you can easily mix any number of lifestyles together using the Lifestyle.CreateHybrid method. Here is an example:
var hybridLifestyle = Lifestyle.CreateHybrid( () => HttpContext.Current != null, new WebRequestLifestyle(), new LifetimeScopeLifestyle()); // Register as hybrid PerWebRequest / PerLifetimeScope. container.Register<DbContext, MyDbContext>(hybridLifestyle);
There is another Stackoverflow question that goes into this subject a bit deeper, you might want to take a look: Simple Injector: multi-threading in MVC3 ASP.NET
UPDATE
About your update. You are almost there. The commands that run on a background thread need to run within a Lifetime Scope, so you will have to start it explicitly. The trick here is to call
BeginLifetimeScope
on the new thread, but before the actual command handler (and its dependencies) is created. In other words, the best way to do this is inside a decorator.The easiest solution is to update your
AsyncCommandHandlerDecorator
to add the scope:public class AsyncCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> where TCommand : ICommand { private readonly Container _container; private readonly Func<ICommandHandler<TCommand>> _factory; public AsyncCommandHandlerDecorator(Container container, Func<ICommandHandler<TCommand>> factory) { _container = container; _factory = factory; } public void Handle(TCommand command) { ThreadPool.QueueUserWorkItem(_ => { using (_container.BeginLifetimeScope()) { // Create new handler in this thread // and inside the lifetime scope. var handler = _factory(); handler.Handle(command); } }); } }
Purists that advocate the SOLID principles will shout that this class is violating the Single Responsibility Principle, since this decorator both runs commands on a new thread and starts a new lifetime scope. I wouldn't worry much about this, since I think that there is a close relationship between starting a background thread and starting a lifetime scope (you wouldn't use one without the other anyway). But still, you could easily leave the
AsyncCommandHandlerDecorator
untouched and create a newLifetimeScopedCommandHandlerDecorator
as follows:public class LifetimeScopedCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> where TCommand : ICommand { private readonly Container _container; private readonly Func<ICommandHandler<TCommand>> _factory; public LifetimeScopedCommandHandlerDecorator(Container container, Func<ICommandHandler<TCommand>> factory) { _container = container; _factory = factory; } public void Handle(TCommand command) { using (_container.BeginLifetimeScope()) { // The handler must be created inside the lifetime scope. var handler = _factory(); handler.Handle(command); } } }
The order in which these decorators are registered is of course essential, since the
AsyncCommandHandlerDecorator
must wrap theLifetimeScopedCommandHandlerDecorator
. This means that theLifetimeScopedCommandHandlerDecorator
registration must come first:container.RegisterDecorator(typeof(ICommandHandler<>), typeof(LifetimeScopedCommandHandlerDecorator<>), backgroundCommandCondition); container.RegisterDecorator(typeof(ICommandHandler<>), typeof(AsyncCommandHandlerDecorator<>), backgroundCommandCondition);
This old Stackoverflow question talks about this in more detail. You should definitely take a look.
这篇关于混合生活方式每个线程和每个Web请求与简单的喷油器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!