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