如何重构Parallel的使用

时间:2013-03-04 11:22:43

标签: c# design-patterns task-parallel-library

在我们的代码库中,我们有很多方法,基本上只是管道,看起来像这样:

    public void Execute()
    {
        _performanceLogger.Log("Start");

        var chunks = GetChunksToWorkOn();

        Parallel.ForEach(chunks , chunk =>
        {
            using (var container = ObjectFactory.Container.GetNestedContainer())
            {
                using (var unitOfWork = new UnitOfWork())
                {
                    container.GetInstance<Worker>().Execute(chunk);
                    unitOfWork.Commit();
                }
            }
        });

        _performanceLogger.Log("Done");
    }

我正在考虑创建一个替换这些调用的模板方法,其内容如下:

public interface IProcessInParallel
{
    void Execute<T>(Func<IEnumerable<IEnumerable<object>>> funcToRetrieveChunksOfWorkForEachParallelProcess) where T : IProcessInParallelTask;
}

public interface IProcessInParallelTask
{
    void DoWork(IEnumerable<object> objects);
}

public class ProcessInParallel : IProcessInParallel
{
    private readonly IPerformanceLogger _performanceLogger;

    public ProcessInParallel(IPerformanceLogger performanceLogger)
    {
        _performanceLogger = performanceLogger;
    }

    public void Execute<T>(Func<IEnumerable<IEnumerable<object>>> funcToRetrieveChunksOfWorkForEachParallelProcess) where T : IProcessInParallelTask
    {
        _performanceLogger.Log("Start");

        var chunks = funcToRetrieveChunksOfWorkForEachParallelProcess.Invoke();

        Parallel.ForEach(chunks, chunk =>
        {
            using (var container = ObjectFactory.Container.GetNestedContainer())
            {
                using (var unitOfWork = new UnitOfWork())
                {
                    container.GetInstance<T>().DoWork(chunk);
                    unitOfWork.Commit();
                }
            }
        });

        _performanceLogger.Log("Done");
    }
}

这种方法的唯一问题是它使用对象......这是丑陋不安全并导致强制转换。

我正在浏览我的模式书但尚未找到合适的解决方案。

有什么建议吗?

2 个答案:

答案 0 :(得分:1)

您可以为 chunk 对象定义一个接口,并使用它代替object

public interface IChunk
{
    // whatever
}

public interface IProcessInParallel
{
    void Execute<T>(Func<IEnumerable<IEnumerable<IChunk>>> getChunks) where T : IProcessInParallelTask;
}

然后你可以传递任何带签名的方法,如:

IEnumerable<IEnumerable<RealChunk>> GetRealChunks()
{
    //...
}

其中RealChunk是实现IChunk的类。这是可能的,thanks to C# 4 variance feature

答案 1 :(得分:1)

你的课程是一个好的开始。您可以通过使用泛型和委托替换继承来使其更轻量化。

  1. 您将T更改为TChunk(更准确的命名)
  2. 块工厂应为Func<TChunk>
  3. 类型
  4. 您需要一个代表在块上执行的工作的委托:Action<IContainer, container>
  5. public void Execute<TChunk>(var _performanceLogger, Func<TChunk> getChunks, Action<IContainer, container> doWork)
        {
            _performanceLogger.Log("Start");
    
            var chunks = getChunks();
    
            Parallel.ForEach(chunks, chunk =>
            {
                using (var container = ObjectFactory.Container.GetNestedContainer())
                {
                    using (var unitOfWork = new UnitOfWork())
                    {
                        doWork(container, chunk);
                        unitOfWork.Commit();
                    }
                }
            });
    
            _performanceLogger.Log("Done");
        }
    }
    
    
    Execute<IEnumerable<object>>(
        logger,
        () => GetChunksToWorkOn(),
        (container, chunk) => container.GetInstance<Worker>().DoWork(chunk));
    

    对此的讽刺肯定是可能的。请注意,我们不依赖于继承。无论如何,接口在逻辑上只不过是功能包。

    通过将container.GetInstance<Worker>()拉入lambda,我们保存了一个泛型类型参数,并使我们的辅助方法更轻量级。这是一种权衡,也可以通过其他方式进行。