混合生活方式每线程和每个Web请求与简单的注射器 [英] Mixed lifestyle for Per Thread and Per Web Request with Simple Injector
问题描述
我正在使用 SimpleInjector
作为我的IoC库。我根据网络请求注册 DbContext
,它工作正常。但是有一个任务是在后台线程中运行它。所以,我有一个问题来创建 DbContext
实例。例如
I'm using SimpleInjector
as my IoC library. I register DbContext
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 create DbContext
instances. e.g.
-
Service1
具有一个DbContext
-
Service2
具有DbContext
从后台线程运行的$>
-
Service1
和Service2
-
Service1
提取实体并将其传递给Service2
-
Service2
使用该实体,但实体与DbContext
$ b分离$ b
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
其实问题在这里: Service1.DbContext
与 Service2不同。 DbContext
。
似乎当我在ASP.NET MVC中的单独的线程中运行任务时, SimpleInjector
为每个呼叫创建一个 DbContext
的新实例。虽然一些IoC库(例如 StructureMap
)对每个线程每个线程请求有混合的生活方式,但似乎 SimpleInjector
没有一个。我是对的吗?
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. While some IoC libraries (for example StructureMap
) have a mixed lifestyle for per-thread-per-webrequest, it seems SimpleInjector
hasn't one. Am I right?
你有什么想法在 SimpleInjector
中解决这个问题吗?
提前感谢
Have you any idea to solve this problem in SimpleInjector
?
Thanks in advance.
编辑
我的服务是这里:
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"
}
我的用户 Service2
有时和 AsyncService2
其他时间。
I user Service2
sometimes and AsyncService2
other times.
推荐答案
似乎当我在ASP.NET MVC中的单独的线程中运行任务时,
SimpleInjector为每个调用创建一个新的DbContext实例。
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.
RegisterPerWebRequest
Simple Injector v1.5及以下版本的生活方式是在Web请求的上下文之外请求实例时返回一个瞬时实例(其中 HttpContext.Current
是空值)。返回一个瞬态实例是简单注射器的设计缺陷,因为这样可以很容易地隐藏不当的使用。 简单注射器版本1.6将抛出异常,而不是错误地返回一个暂时的实例,清楚地表明您已经错误配置了该容器。
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 (where HttpContext.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.
虽然一些IoC库(例如StructureMap)有一个混合的
每个线程每个网络请求的生活方式,似乎简单的注入
没有一个
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.
虽然你可以混合每个Web请求,其中每线程生活方式,当您将每个Web请求与每个生命周期范围,因为在终身职业范围内,您明确地开始并完成了范围(并且可以在范围结束时配置 DbContext
)。
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).
从简单注射器2 然后,您可以使用 Lifestyle.CreateHybrid 方法。这里有一个例子:
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);
还有另一个Stackoverflow问题进入这个主题,你可能想看看:简单注射器:MVC3 ASP.NET中的多线程
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
更新
关于您的更新。你几乎在那里在后台线程上运行的命令需要在生命周期范围中运行,因此您必须明确地启动它。这里的技巧是在新线程上调用 BeginLifetimeScope
,但在创建实际的命令处理程序(及其依赖项)之前。换句话说,最好的方法是在装饰器中。
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.
最简单的解决方案是更新您的 AsyncCommandHandlerDecorator
添加范围:
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);
}
});
}
}
倡导 SOLID 原则会喊出这个类违反了单一责任原则,因为这个装饰器都在新线程上运行命令并启动新的生命周期范围。我不用担心这一点,因为我认为启动后台线程和启动生命周期范围之间有一个密切的关系(你不会使用一个没有其他的)。但是,您仍然可以轻松地将 AsyncCommandHandlerDecorator
保持不变,并创建一个新的 LifetimeScopedCommandHandlerDecorator
,如下所示:
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 new LifetimeScopedCommandHandlerDecorator
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);
}
}
}
这些装饰器的顺序注册当然是必需的,因为 AsyncCommandHandlerDecorator
必须包装 LifetimeScopedCommandHandlerDecorator
。这意味着 LifetimeScopedCommandHandlerDecorator
注册必须先到:
The order in which these decorators are registered is of course essential, since the AsyncCommandHandlerDecorator
must wrap the LifetimeScopedCommandHandlerDecorator
. This means that the LifetimeScopedCommandHandlerDecorator
registration must come first:
container.RegisterDecorator(typeof(ICommandHandler<>),
typeof(LifetimeScopedCommandHandlerDecorator<>),
backgroundCommandCondition);
container.RegisterDecorator(typeof(ICommandHandler<>),
typeof(AsyncCommandHandlerDecorator<>),
backgroundCommandCondition);
这个旧的Stackoverflow问题更详细地谈到这一点。你一定要看看。
This old Stackoverflow question talks about this in more detail. You should definitely take a look.
这篇关于混合生活方式每线程和每个Web请求与简单的注射器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!