3

我正在开发一个 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 是在请求​​的整个生命周期内启动的,我想仅针对一个控制器中的一种方法跳过该对象的垃圾收集。我试图将存储库作为参数传递给方法和委托,但这并没有延长它的生命周期。

4

1 回答 1

4

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

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

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

更进一步,实际的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();
    }
}

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

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

于 2011-12-22T14:04:58.307 回答