简体   繁体   English

在MVC3应用程序中为异步处理程序保留依赖注入对象

[英]Keep dependency injected objects for async handler in MVC3 application

I am working on a MVC3 application in which I would like to handle some code blocks asynchronously. 我正在研究一个MVC3应用程序,我想在其中异步处理一些代码块。 These code blocks need to perform some db operations and they need access to my DAL repositories which are initiated trough my dependency injection setup. 这些代码块需要执行一些数据库操作,并且需要访问我的DAL存储库,这些存储库是通过我的依赖注入设置启动的。 I have set the lifetime of the repository to an "instance per http request" lifetime and I am looking for a way to extend that lifetime for the async operation. 我已将存储库的生命周期设置为“每个http请求的实例”生命周期,我正在寻找一种方法来延长异步操作的生命周期。

public class AsyncController : Controller
{
    private readonly ICommandBus commandBus;
    private readonly IObjectRepository objectRepository;

    public ActionResult DoThings()
    {
        DoThingsAsync();
        return View();
    }

    private delegate void DoThingsDelegate();
    private void DoThingsAsync()
    {
        var handler = new DoThingsDelegate(DoThingsNow);
        handler.BeginInvoke(null, null);
    }

    private void DoThingsNow()
    {
        var objects = objectRepository.getAll();
        foreach (Thing thing in objects)
        {
            thing.modifiedOn = DateTime.Now;
            objectRepository.Update(thing);
        }
    }
}  

The objectRepository is initiated for the lifetime of the request and I would like to skip the garbage collection for this object for one method in one controller only. objectRepository是在请求的生命周期内启动的,我想在一个控制器中跳过一个方法的垃圾收集。 I have tried to pass the repository as a parameter to the method and the delegate but that did not extend its lifetime. 我试图将存储库作为参数传递给方法和委托,但这并未延长其生命周期。

When starting a new thread, it is wise to let the Container compose a totally new object graph for you. 在开始一个新线程时,最好让Container为你创建一个全新的对象图。 When you reuse dependencies that are created in the context of a HTTP request (or thread context or what have you), this can result in race conditions and other sort of failures and strange behavior. 当您重用在HTTP请求(或线程上下文或您拥有的内容)的上下文中创建的依赖项时,这可能会导致竞争条件和其他类型的故障和奇怪的行为。

This of course doesn't have to be the case when you know that those dependencies are thread-safe (or created with a per-request lifetime and are not used by the request after triggering the async operation), but this is not something a consumer of these dependencies should know or should depend on, because it is the responsibility of the part of the application that wires everything together (The Composition Root ). 当然,当您知道这些依赖项是线程安全的(或者使用每个请求生命周期创建并且在触发异步操作后请求不使用这些依赖项)时,情况并非必须如此,但这不是这些依赖项的使用者应该知道或应该依赖,因为应用程序的一部分负责将所有内容连接在一起( 组合根 )。 Doing this, would also make it harder to change that dependency configuration later on. 这样做,也会使以后更改该依赖关系配置变得更加困难。

Instead, you should refactor your DoThings method into its own class and let your Controller depend on some sort of IDoThings interface. 相反,您应该将DoThings方法重构为自己的类,并让Controller依赖于某种IDoThings接口。 This way you can postpone the decision about handling things asynchronously until the moment you wire everything together. 通过这种方式,您可以推迟有关异步处理事物的决定,直到您将所有内容连接在一起为止。 When reusing that logic in another type of application (Windows Service for instance), it even allows you to change the way this operation is executed asynchronously (or simply execute it synchronously). 在其他类型的应用程序(例如Windows Service)中重用该逻辑时,它甚至允许您更改此操作异步执行的方式(或者只是同步执行它)。

To go even one step further, the actual DoThings business logic and the part that executes that logic asynchronously are two different concerns: two separate responsibilities. 更进一步,实际的DoThings业务逻辑和异步执行该逻辑的部分是两个不同的问题:两个不同的职责。 You should separate them into different classes. 您应该将它们分成不同的类。

Here's an example of what I advise you to do: 这是我建议你做的一个例子:

Define an interface: 定义一个接口:

public interface IDoThings
{
    void DoThings();
}

Let your Controller depend on that interface: 让您的Controller依赖于该接口:

public class SomeController : Controller
{
    private readonly IDoThings thingsDoer;

    public SomeController(IDoThings thingsDoer)
    {
         this.thingsDoer = thingsDoer;
    }

    public ActionResult DoThings()
    {
        this.thingsDoer.DoThings();
        return View();
    }
}

Define an implementation that contains the business logic: 定义包含业务逻辑的实现:

public class DoingThings : IDoThings
{
    private readonly ICommandBus commandBus;
    private readonly IObjectRepository objectRepository;

    public DoingThings(ICommandBus commandBus,
        IObjectRepository objectRepository)
    {
        this.commandBus = commandBus;
        this.objectRepository = objectRepository;
    }

    public void DoThings()
    {
        var objects = objectRepository.getAll();

        foreach (Thing thing in objects)
        {
            thing.modifiedOn = DateTime.Now;
            objectRepository.Update(thing);
        }
    }
}

Define a proxy that knows how to handle a DoingThings asynchronously: 定义一个知道如何异步处理DoingThings的代理:

public class DoingThingsAsync : IDoThings
{
    private readonly Container container;

    public DoingThingsAsync(Container container)
    {
        this.container = container;
    }

    public void DoThings()
    {
        Action handler = () => DoThingsNow();
        handler.BeginInvoke(null, null);        
    }

    private void DoThingsNow()
    {
        // Here we run in a new thread and HERE we ask
        // the container for a new DoingThings instance.
        // This way we will be sure that all its
        // dependencies are safe to use. Never move
        // dependencies from thread to thread.
        IDoThings doer =
            this.container.GetInstance<DoingThings>();

        doer.DoThings();
    }
}

Now, instead of injecting a DoingThings into the SomeController , you inject a DoingThingsAsync into the controller. 现在,不是将DoingThings注入SomeController ,而是将一个DoingThingsAsync注入控制器。 The controller does not know whether the operation is executed synchronously or not, and it doesn't care. 控制器不知道操作是否同步执行,并不关心。 Besides that, this way you are also separating your business logic from your presentation logic, which is important for lots of good reasons. 除此之外,通过这种方式,您还可以将业务逻辑与表示逻辑分开,这很多原因很重要。

You might want to consider using the command pattern as basis around business operations that mutate state (if you're not already using such a thing, considering the ICommandBus interface). 您可能需要考虑使用命令模式作为改变状态的业务操作的基础(如果您还没有使用这样的东西,请考虑ICommandBus接口)。 Take for instance a look at this article . 举个例子来看看这篇文章 With this pattern you can more easily configure certain commands to run asynchronously or batch them to an external transactional queue, for later processing, without having to change any of the consumers of those commands. 使用此模式,您可以更轻松地将某些命令配置为异步运行或将它们批处理到外部事务队列,以便以后处理,而无需更改这些命令的任何使用者。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM