2

我有一些如下所示的代码。我是 IoC 和 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()
    {...}
}
4

2 回答 2

5

应用依赖注入时,您希望将相关对象的图形组合的控制移动到应用程序中称为Composition Root的单个位置。

为了消除应用程序其余部分的复杂性,只有组合根应该知道要创建哪些实例以及如何创建它们。组合根是唯一知道对象图是如何构建的,并且它知道(或者至少在编写 DI 配置时应该知道)服务是否可以安全地相互依赖。应该在这里发现线程安全问题。

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

所以一般建议是:

  • 让 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(应该只包含机制)移动到组合根中,从而防止使用服务定位器反模式

本文解释了有关在多线程应用程序中使用 DI 的更多信息。请注意,它是针对不同框架的文档,但一般建议是相同的。

于 2013-05-31T15:25:59.497 回答
0

如果您需要OtherClass在循环的每次迭代中创建新实例,您的代码并不明显?我想 IOtherClass 接口包含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>();
于 2013-05-31T15:18:25.173 回答