混合生活方式每线程和每个Web请求与简单的注射器 [英] Mixed lifestyle for Per Thread and Per Web Request with Simple Injector

查看:99
本文介绍了混合生活方式每线程和每个Web请求与简单的注射器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 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.


  1. Service1 具有一个 DbContext

  2. Service2 具有 DbContext
  3. Service1 Service2

  4. Service1 提取实体并将其传递给 Service2

  5. Service2 使用该实体,但实体与 DbContext
  6. $ b分离$ b
  1. Service1 has an instance of DbContext
  2. Service2 has an instance of DbContext
  3. Service1 and Service2 run from background thread.
  4. Service1 fetches an entity and pass it to Service2
  5. Service2 uses that entity, but entity is detached from DbContext

其实问题在这里: 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆