При применении инъекции зависимостей, вы хотите, чтобы переместить контроль состава графиков соответствующего объекта в одном месте в приложении под названием Composition Root.
Чтобы удалить сложность в остальной части приложения, только созданный корень должен знать, какие экземпляры создавать и как их создавать. Корень состава - единственный, кто знает, как строятся графические объекты, и он знает (или, по крайней мере, при написании конфигурации DI, которую вы должны знать), если службы могут безопасно зависеть друг от друга. Здесь следует определить проблемы безопасности потока.
Сама реализация не должна знать, безопасно ли использовать зависимость; он должен уметь предположить, что эта зависимость безопасна для использования в потоке, в котором она запущена. Помимо обычного временного и одноэлементного образа жизни (где переходный процесс означает, что создается новый экземпляр, где singleton означает, что приложение будет иметь только один экземпляр этой службы), часто бывают другие образы жизни, которые имеют сходство потоков. Возьмем, к примеру, образ жизни в Интернете.
Так общий совет заключается в следующем:
- Пусть контейнер IoC разрешить все объекты для вас, и
- Не двигайтесь услугами от нити нити.
Вашей SomeService
реализация является правильной с точки зрения безопасности потоков, поскольку каждый поток создает свой собственный новый (переходный) OtherClass
экземпляра. Но это начинает становиться проблематичным, когда OtherClass
начинает иметь собственные зависимости.В этом случае вы должны перенести создание этого объекта в свой корневой каталог. Тем не менее, если вы реализуете, что, как показано в следующем примере, вы будете иметь проблемы:
public class SomeService
{
private IOtherClass other;
public SomeService(IOtherClass other)
{
this.other = other;
}
public void Operate(Items items)
{
Parallel.Foreach(items, item => this.other.DoWork(item));
}
}
Реализация является проблематичным, поскольку SomeService
перемещает один IOtherClass
экземпляр по потокам и предполагает, что IOtherClass
потокобезопасно, который знание, которое должен знать только корневой состав. Диспергирование этих знаний во всем приложении увеличивает сложность.
При работе с многопоточным кодом каждый поток должен получить свой собственный граф объектов. Это означает, что при запуске нового потока вы должны снова запросить контейнер/ядро для корневого объекта и вызвать этот объект. Вот так:
public class SomeService
{
private IItemProcessor processor;
public SomeService(IItemProcessor processor)
{
this.processor = processor;
}
public void Operate(Items items)
{
this.processor.Process(items);
}
}
public class ItemProcessor : IItemProcessor
{
private IKernel container;
public ItemProcessor(IKernel container)
{
this.container = container;
}
public void Process(Items items)
{
Parallel.Foreach(items, item =>
{
// request a new IOtherClass again on each thread.
var other = this.container.Get<IOtherClass>();
other.DoWork(item);
});
}
}
Это намеренно извлекает механику обработки этих предметов в другой класс. Это позволяет сохранить бизнес-логику в SomeService
и позволяет перемещать ItemProcessor
(который должен содержать только механики) в корень композиции, поэтому предотвращается использование Service Locator anti-pattern.
This article объясняет больше о работе с DI в многопоточных приложениях. Обратите внимание, что это документация для другой структуры, но общие рекомендации одинаковы.
Благодарим вас за отличное объяснение и пример. Я смог понять и реализовать! – NBPC77
Инъекция IKernel в ItemProcessor не является хорошей идеей, потому что бизнес-класс знает о Ninject. Лучший способ - ввести делегата, например Func. –
mtkachenko
@mtkachenko: Как я объяснил в своем ответе, «ItemProcessor» должен быть частью корня композиции, что означает, что он * не * бизнес-компонент больше, а инфраструктурный компонент. – Steven