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

时间:2011-12-22 09:19:56

标签: c# asp.net-mvc-3 asynchronous dependency-injection operations

我正在开发一个MVC3应用程序,我想在其中异步处理一些代码块。这些代码块需要执行一些数据库操作,并且需要访问我的DAL存储库,这些存储库是通过我的依赖注入设置启动的。我已经将存储库的生命周期设置为每个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);
        }
    }
}  

objectRepository是在请求的生命周期内启动的,我想在一个控制器中跳过一个方法的垃圾收集。我试图将存储库作为参数传递给方法和委托,但这并未延长其生命周期。

1 个答案:

答案 0 :(得分:4)

当开始一个新线程时,让Container为你构成一个全新的对象图是明智的。当您重用在HTTP请求(或线程上下文或您拥有的内容)的上下文中创建的依赖项时,这可能会导致竞争条件和其他类型的失败和奇怪的行为。

当然,当您知道这些依赖项是线程安全的(或者使用每个请求生存期创建并且在触发异步操作后请求不使用这些依赖项)时,当然不一定是这种情况,但这是这些依赖关系的消费者不应该知道或应该依赖什么,因为应用程序的一部分责任将所有连接在一起(Composition Root)。这样做也会使以后更改依赖配置变得更加困难。

相反,您应该将DoThings方法重构为自己的类,并让Controller依赖于某种IDoThings接口。通过这种方式,您可以推迟有关异步处理事物的决定,直到您将所有内容连接在一起为止。在其他类型的应用程序(例如Windows Service)中重用该逻辑时,它甚至允许您更改此操作异步执行的方式(或者只是同步执行它)。

更进一步,实际的DoThings业务逻辑和异步执行该逻辑的部分是两个不同的问题:两个不同的职责。 You should separate them分为不同的班级。

以下是我建议你做的一个例子:

定义界面:

public interface IDoThings
{
    void DoThings();
}

让您的Controller依赖于该界面:

public class SomeController : Controller
{
    private readonly IDoThings thingsDoer;

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

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

定义包含业务逻辑的实现:

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);
        }
    }
}

定义一个知道如何异步处理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();
    }
}

现在,您不是将DoingThings注入SomeController,而是将DoingThingsAsync注入控制器。控制器不知道操作是否同步执行,并不关心。除此之外,通过这种方式,您还可以将业务逻辑与表示逻辑分开,这很有道理。

您可能需要考虑使用命令模式作为改变状态的业务操作的基础(如果您还没有使用这样的东西,请考虑ICommandBus接口)。以this article为例。使用此模式,您可以更轻松地将某些命令配置为异步运行或将它们批处理到外部事务队列,以便以后处理,而无需更改这些命令的任何使用者。