Сохранять зависимые объекты для асинхронного обработчика в приложении MVC3 - PullRequest
3 голосов
/ 22 декабря 2011

Я работаю над приложением 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 Ответ

4 голосов
/ 22 декабря 2011

При запуске нового потока целесообразно позволить Контейнеру составить для вас совершенно новый граф объектов. При повторном использовании зависимостей, созданных в контексте HTTP-запроса (или контекста потока, или чего-либо еще), это может привести к условиям гонки и другим видам сбоев и странному поведению.

Конечно, это не обязательно тот случай, когда вы знаете, что эти зависимости являются поточно-ориентированными (или создаются с временем жизни для каждого запроса и не используются запросом после запуска асинхронной операции), но это не то, что потребитель этих зависимостей должен знать или должен зависеть, потому что именно часть приложения связывает все вместе ( Composition Root ). Это также затруднит дальнейшее изменение этой конфигурации зависимостей.

Вместо этого вы должны реорганизовать ваш 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). Взять, к примеру, эту статью . С помощью этого шаблона вы можете легче настроить некоторые команды для асинхронного выполнения или пакетировать их во внешнюю транзакционную очередь для последующей обработки, не изменяя ни одного из потребителей этих команд.

...