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

本文关键字:保留 程序 依赖 注入 对象 处理 异步 MVC3 应用程序 | 更新日期: 2023-09-27 18:18:39

我正在一个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);
        }
    }
}  

objectrerepository是在请求的生命周期内启动的,我希望仅为一个控制器中的一个方法跳过此对象的垃圾收集。我已经尝试将存储库作为参数传递给方法和委托,但这并没有延长其生存期。

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

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

当然,当你知道这些依赖是线程安全的(或者在每个请求生命周期中创建,并且在触发异步操作后不会被请求使用)时,情况就不一定是这样了,但是这些依赖的消费者不应该知道或应该依赖于这些依赖,因为这是将所有东西连接在一起的应用程序部分的责任(组合根)。这样做也会使以后更改依赖项配置变得更加困难。

相反,你应该重构你的DoThings方法到它自己的类中,让你的控制器依赖于某种IDoThings接口。通过这种方式,您可以将有关异步处理的决定推迟到将所有内容连接在一起的那一刻。当在另一种类型的应用程序(例如Windows Service)中重用该逻辑时,它甚至允许您更改该操作异步执行的方式(或简单地同步执行)。

更进一步,实际的DoThings业务逻辑和异步执行该逻辑的部分是两个不同的关注点:两个独立的职责。你应该把它们分成不同的类。

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

定义接口:
public interface IDoThings
{
    void DoThings();
}

让你的控制器依赖于这个接口:

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接口)。以这篇文章为例。使用此模式,您可以更轻松地配置某些命令以异步运行或将它们批处理到外部事务队列以供稍后处理,而无需更改这些命令的任何使用者。