使用简单注入器实现每线程和每 Web 请求的混合生活方式 [英] Mixed lifestyle for Per Thread and Per Web Request with Simple Injector

查看:14
本文介绍了使用简单注入器实现每线程和每 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. 的实例
  3. Service2 有一个 DbContext 的实例
  4. Service1Service2 从后台线程运行.
  5. Service1 获取实体并将其传递给 Service2
  6. Service2 使用该实体,但实体与 DbContext
  7. 分离
  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.DbContextService2.DbContext 不同.

Actually the problem is here: Service1.DbContext is difference from Service2.DbContext.

似乎当我在 ASP.NET MVC 中的单独线程中运行任务时,SimpleInjector 为每次调用创建一个新的 DbContext 实例.虽然一些 IoC 库(例如 StructureMap)对于 per-thread-per-webrequest 具有混合的生活方式,但似乎 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.

Simple Injector v1.5 及以下版本的 RegisterPerWebRequest 生活方式的行为是在 Web 请求上下文之外请求实例时返回一个瞬态实例(其中 HttpContext.Current 为空).返回一个瞬态实例是 Simple Injector 的一个设计缺陷,因为这很容易隐藏不当使用.1.6 版 Simple Injector 将抛出异常而不是错误地返回瞬态实例,清楚地表明您错误地配置了容器.

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)有一个混合的per-thread-per-webrequest 的生活方式,似乎是 Simple Injector没有

While some IoC libraries (for example StructureMap) have a mixed lifestyle for per-thread-per-webrequest, it seems Simple Injector hasn't one

由于几个原因,Simple Injector 没有内置对混合生活方式的支持是正确的.首先,这是一个非常奇特的功能,并不是很多人需要的.其次,您可以将任意两种或三种生活方式混合在一起,这样几乎可以形成无穷无尽的混合体.最后,自己注册(非常)容易.

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.

虽然您可以将每个网络请求Per Thread 生活方式,将 Per Web Request 与 Per Lifetime Scope,因为使用 Lifetime Scope 您可以明确地开始和结束范围(和可以在作用域结束时处理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).

Simple Injector 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

更新

关于您的更新.你快到了.在后台线程上运行的命令需要在 Lifetime Scope 内运行,因此您必须明确启动它.这里的技巧是在新线程上调用 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天全站免登陆