并行处理的依赖注入

本文关键字:注入 依赖 并行处理 | 更新日期: 2023-09-27 18:09:28

我正在尝试手工依赖注入(还没有框架)来消除代码中的紧密耦合。这只是为了练习-我没有一个具体的实现在脑海中。

到目前为止,简单的构造函数注入工作得很好。

然而,我不知道如何解决创建一个紧密耦合时,一个类必须使用另一个并行循环。例子:

public class Processor
{
    private IWorker worker;
    public Processor(IWorker worker)
    {
        this.worker = worker;
    }
    public List<string> DoStuff()
    {
        var list = new List<string>();
        for (int i = 0; i < 10; i++)
        {
            list.Add(worker.GetString());
        }
        return list;
    }
    public List<string> DoStuffInParallel()
    {
        var list = new System.Collections.Concurrent.ConcurrentBag<string>();
        Parallel.For(0, 10, i => 
        { 
            //is there any trivial way to avoid this??
            list.Add(new Worker().GetString());
        });
        return list.ToList();
    }
}
public class Worker : IWorker
{
    public string GetString()
    {
        //pretend this relies on some instance variable, so it not threadsafe
        return "a string";
    }
}
在上述情况下,为了避免并行循环比标准循环慢这一明显事实,我如何编写Processor.DoStuffInParallel()方法来避免当前对Worker类的硬依赖?

并行处理的依赖注入

解耦的一种方法是注入工厂,例如:

public List<string> DoStuffInParallel(IWorkerFactory factory)
{
    var list = new System.Collections.Concurrent.ConcurrentBag<string>();
    Parallel.For(0, 10, i => 
    { 
        list.Add(factory.Create().GetString());
    });
    return list.ToList();
}

工厂可以是容器拥有的单例,并且Create()需要是线程安全的。

当然,请注意,您的任务不能并发地改变列表-您需要同步访问时,将工作结果添加到列表 (apols,错过了您的ConcurrentBag )-为了减少bag上的争用,您可能还希望查看Parallel.For重载之一,使用localinit/localFinally将结果本地聚合到每个任务列表,然后同步到localFinally中的聚合/总体包。

编辑
回复:我需要为ConcurrentBag<String>注入工厂吗?在我看来,直接创建ConcurrentBag是可以的——它是一个具体的实现细节,而不是一个依赖项。例如,单线程实现可以这样实现:

return Enumerable.Range(0, 10)
                 .Select(i => factory.Create().GetString())
                 .ToList();

。没有任何中间容器的显式构造。

您可以选择将方法的接口软化为public IList<string> DoStuffInParallel甚至IEnumerable<string>(可能的最小契约/承诺)。这里的依赖关系是在Worker上,这是您希望能够在单元测试中模拟的。