如何在 Parallel.ForEach 循环中使用类的依赖关系,同时练习依赖注入/IoC

问题描述 投票:0回答:2

我有一些如下所示的代码。我是控制反转和 Ninject 的新手,不知道如何将

IOtherClass
注入
SomeService
以便
Parallel.ForEach
工作。我唯一的另一个猜测是将
Parallel.ForEach
移到
DoWork
方法中?

感谢您的帮助。

public class SomeService
{
    Parallel.Foreach(items, item => new OtherClass.DoWork(item);
}

public class OtherClass:IOtherClass
{
    public void DoWork()
    {...}
}
c# inversion-of-control ninject
2个回答
6
投票

应用依赖注入时,您希望将相关对象的图形组合的控制移动到应用程序中称为“组合根”的单个位置。 为了消除应用程序其余部分的复杂性,只有组合根应该知道要创建哪些实例以及如何创建它们。组合根是唯一知道如何构建对象图的根,它知道(或者至少在编写 DI 配置时您应该知道)服务是否可以安全地相互依赖。线程安全问题应该在这里被发现。

实现本身不必知道使用依赖项是否安全;它必须能够“假设”该依赖项可以安全地在其运行的线程上使用。除了通常的瞬态和单例生活方式(其中瞬态意味着创建一个新实例,而单例意味着应用程序将只有该服务的一个实例)之外,通常还有其他具有线程亲和力的生活方式。以“按网络请求”生活方式为例。

所以一般建议是:

让 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);
}

// Due to the dependency on IKernel, this class should be part of
// your Composition Root.
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

(应仅包含机制)移动到组合根中,从而防止使用

服务定位器反模式
本文解释了有关在多线程应用程序中使用 DI 的更多信息。请注意,它是不同框架的文档,但一般建议是相同的。

如果您需要在循环的每次迭代中创建

OtherClass

0
投票
DoWork

方法? 如果您可以在每次迭代中使用相同的实例,那么正确的方法是构造函数注入。


SomeService
的构造函数中注入

IOtherClass


public class SomeService { private readonly IOtherClass _otherClass; public SomeService(IOtherClass otherClass) { _otherClass = otherClass; } void YourLoopMethod() { Parallel.Foreach(items, item =>_otherClass.DoWork(item)); } }

如果您在循环的每次迭代中都需要新实例,那么您可能可以使用注入到的“其他类工厂”

SomeService


public interface IOtherClassFactory { IOtherClass Create(); } public class SomeService { private readonly IOtherClassFactory _otherClassFactory; public SomeService(IOtherClassFactory otherClassFactory) { _otherClassFactory = otherClassFactory; } void YourLoopMethod() { Parallel.Foreach(items, item => _otherClassFactory.Create().DoWork(item)); } }


这里您需要实现知道如何创建

IOtherClassFactory
 对象的 
IOtherClass

。在您的实现中,您可以注入

IKernel
依赖项并使用它来创建
IOtherClass
对象。

对于这两种情况,您都需要在组合根目录中注册 Ninject 内核绑定。

kernel.Bind<IOtherClas>().To<OtherClass>();

© www.soinside.com 2019 - 2024. All rights reserved.