PerRequestLifetimeManager和Task.Factory.StartNew-Unity的依赖注入 [英] PerRequestLifetimeManager and Task.Factory.StartNew - Dependency Injection with Unity

本文介绍了PerRequestLifetimeManager和Task.Factory.StartNew-Unity的依赖注入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何使用PerRequestLifeTimeManager管理新任务? 我应该在新任务中创建另一个容器吗?(我不想将PerRequestLifeTimeManager更改为PerResolveLifetimeManager/HierarchicalLifetimeManager)

How to manage new tasks with PerRequestLifeTimeManager? Should I create another container inside a new task?(I wouldn't like to change PerRequestLifeTimeManager to PerResolveLifetimeManager/HierarchicalLifetimeManager)

    [HttpPost]
    public ActionResult UploadFile(FileUploadViewModel viewModel)
    {
        var cts = new CancellationTokenSource();
        CancellationToken cancellationToken = cts.Token;
        Task.Factory.StartNew(() =>
        {
            // _fileService =  DependencyResolver.Current.GetService<IFileService>();
            _fileService.ProcessFile(viewModel.FileContent);

        }, cancellationToken);
    }

推荐答案

您应该阅读这篇有关DI的文章在多线程应用程序中.尽管它是为不同的DI库编写的,但您通常会发现大多数适用于DI概念的信息.引用一些重要的部分:

You should read this article about DI in multi-threaded applications. Although it is written for a different DI library, you'll find most of the information applicable to the concept of DI in general. To quote a few important parts:

依赖注入会强制您将所有依赖项连接到一个 在应用程序中的单个位置:组成根.这意味着 在应用程序中只有一个地方知道如何 服务的行为,它们是否是线程安全的以及应该如何运行 有线.没有这种集中化,这些知识将分散 在整个代码库中,很难更改行为 服务.

Dependency injection forces you to wire all dependencies together in a single place in the application: the Composition Root. This means that there is a single place in the application that knows about how services behave, whether they are thread-safe, and how they should be wired. Without this centralization, this knowledge would be scattered throughout the code base, making it very hard to change the behavior of a service.

在多线程应用程序中,每个线程应获得其自己的对象 图形.这意味着您通常应该致电 [Resolve< T>()]在线程的开始处执行一次 执行以获取用于处理该线程的根对象(或 要求).容器将建立一个具有所有根的对象图 对象的依赖关系.这些依赖关系中的一些将是单例. 在所有线程之间共享.其他依赖关系可能是暂时的.一种 每个依赖项都会创建一个新实例.其他依赖关系可能是 特定于线程,特定于请求或具有其他生活方式.这 应用程序代码本身不知道依赖项的方式 注册,这就是应该的样子.

In a multi-threaded application, each thread should get its own object graph. This means that you should typically call [Resolve<T>()] once at the beginning of the thread’s execution to get the root object for processing that thread (or request). The container will build an object graph with all root object’s dependencies. Some of those dependencies will be singletons; shared between all threads. Other dependencies might be transient; a new instance is created per dependency. Other dependencies might be thread-specific, request-specific, or with some other lifestyle. The application code itself is unaware of the way the dependencies are registered and that’s the way it is supposed to be.

在开始时建立新对象图的建议 线程,在手动启动新的(后台)线程时也保持有效. 尽管您可以将数据传递给其他线程,但不应传递 容器控制的对其他线程的依赖关系.在每个新的 线程,则应再次向容器询问相关性.什么时候 您开始将依赖关系从一个线程传递给另一个线程 部分代码必须知道通过这些代码是否安全 依赖.例如,那些依赖关系是线程安全的吗? 在某些情况下分析起来可能很琐碎,但会阻止您 从现在开始,您就可以通过其他实现来更改这些依赖关系 必须记住,您的代码中有一个地方 发生,您需要知道传递了哪些依赖项.你 再次将知识分散化,这使推理变得更加困难 关于您的DI配置的正确性并使其更容易 错误配置容器会导致并发问题.

The advice of building a new object graph at the beginning of a thread, also holds when manually starting a new (background) thread. Although you can pass on data to other threads, you should not pass on container-controlled dependencies to other threads. On each new thread, you should ask the container again for the dependencies. When you start passing dependencies from one thread to the other, those parts of the code have to know whether it is safe to pass those dependencies on. For instance, are those dependencies thread-safe? This might be trivial to analyze in some situations, but prevents you to change those dependencies with other implementations, since now you have to remember that there is a place in your code where this is happening and you need to know which dependencies are passed on. You are decentralizing this knowledge again, making it harder to reason about the correctness of your DI configuration and making it easier to misconfigure the container in a way that causes concurrency problems.

因此,您不应从应用程序代码本身内部旋转新线程.而且,您绝对不应创建新的容器实例,因为这可能会导致各种性能问题.通常,每个应用程序应该只有一个容器实例.

So you should not spin of new threads from within your application code itself. And you should definitely not create a new container instance, since this can cause all sorts of performance problems; you should typically have just one container instance per application.

相反,您应该将此基础结构逻辑纳入您的组合根,它可以简化控制器的代码.您的控制器代码不得超过此值:

Instead, you should pull this infrastructure logic into your Composition Root, which allows your controller's code to be simplified. Your controller code should not be more than this:

[HttpPost]
public ActionResult UploadFile(FileUploadViewModel viewModel)
{
    _fileService.ProcessFile(viewModel.FileContent);
}

另一方面,您不想更改IFileService实现,因为它不关心执行多线程.相反,我们需要一些基础结构逻辑,可以将它们放置在控制器和文件服务之间,而无需他们对此有所了解.他们的方法是通过为文件服务实现代理类并将其放置在您的合成根目录"中:

On the other hand, you don't want to change the IFileService implementation, because it shouldn't its concern to do multi-threading. Instead we need some infrastructural logic that we can place in between the controller and the file service, without them having to know about this. They way to do this is by implementing a proxy class for the file service and place it in your Composition Root:

private sealed class AsyncFileServiceProxy : IFileService {
    private readonly ILogger logger;
    private readonly Func<IFileService> fileServiceFactory;
    public AsyncFileServiceProxy(ILogger logger, Func<IFileService> fileServiceFactory)
    {
        this.logger = logger;
        this.fileServiceFactory = fileServiceFactory;
    }

    void IFileService.ProcessFile(FileContent content) {
        // Run on a new thread
        Task.Factory.StartNew(() => {
            this.BackgroundThreadProcessFile(content);
        });
    }

    private void BackgroundThreadProcessFile(FileContent content) {
        // Here we run on a different thread and the
        // services should be requested on this thread.
        var fileService = this.fileServiceFactory.Invoke();

        try {
            fileService.ProcessFile(content);
        } 
        catch (Exception ex) {
            // logging is important, since we run on a
            // different thread.
            this.logger.Log(ex);
        }
    }
}

此类是基础架构逻辑的小和平,它允许在后台线程上处理文件.剩下的唯一事情就是将容器配置为注入我们的AsyncFileServiceProxy而不是真实的文件服务实现.有多种方法可以做到这一点.这是一个示例:

This class is a small peace of infrastructural logic that allows processing files on a background thread. The only thing left is to configure the container to inject our AsyncFileServiceProxy instead of the real file service implementation. There are multiple ways to do this. Here's an example:

container.RegisterType<ILogger, YourLogger>();
container.RegisterType<RealFileService>();
container.RegisterType<Func<IFileService>>(() => container.Resolve<RealFileService>(),
    new ContainerControlledLifetimeManager());
container.RegisterType<IFileService, AsyncFileServiceProxy>();

但是,方程式中缺少一部分,这就是如何处理有范围的生活方式,例如按请求的生活方式.由于您是在后台线程上运行内容,因此没有HTTPContext,这基本上意味着您需要启动一些作用域"来模拟请求(因为后台线程基本上是它自己的新请求).但是,这是我对Unity的了解停止的地方.我对Simple Injector和Simple Injector非常熟悉,您将使用混合生活方式(将每个请求的生活方式与生命周期范围的生活方式混合在一起)来解决此问题,并且在此范围内明确包装了对BackgroundThreadProcessFile的调用.我想Unity中的解决方案与此非常接近,但是不幸的是,我对Unity的了解不足,无法向您展示如何实现.希望其他人可以对此发表评论,或添加一个额外的答案以解释如何在Unity中做到这一点.

One part however is missing here from the equation, and this is how to deal with scoped lifestyles, such as the per-request lifestyle. Since you are running stuff on a background thread, there is no HTTPContext and this basically means that you need to start some 'scope' to simulate a request (since your background thread is basically its own new request). This is however where my knowledge about Unity stops. I'm very familiar with Simple Injector and with Simple Injector you would solve this using a hybrid lifestyle (that mixes a per-request lifestyle with a lifetime-scope lifestyle) and you explicitly wrap the call to BackgroundThreadProcessFile in such scope. I imagine the solution in Unity to be very close to this, but unfortunately I don't have enough knowledge of Unity to show you how. Hopefully somebody else can comment on this, or add an extra answer to explain how to do this in Unity.

这篇关于PerRequestLifetimeManager和Task.Factory.StartNew-Unity的依赖注入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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