并行处理 - 管理父/子依赖关系

时间:2014-05-16 13:51:44

标签: c# parallel-processing task-parallel-library

我正在Parallel.ForEach处理包含员工及其家属的文本文件。订单无法保证在文件中。在每次迭代中,我都在创建一个员工或从属对象。员工对象被添加到ConcurrentDictionary。受抚养人是雇员的财产,需要与雇员联系。问题是如果员工不在字典中,当我尝试添加依赖项时,从属组件永远不会被添加。我可以缓存这些“孤儿”家属,并在ForEach完成后添加它们,但我认为有更好的方法。

在添加员工之前,我是否有某种方法可以等待/旋转/加入我的Parallel.ForEach,然后添加依赖项。我没有和这个解决方案结婚,所以欢迎替代方案。

这是我的代码(为简洁起见编辑):

var cx = System.IO.File.ReadLines(path);
var _employeeDictionary = new ConcurrentDictionary<string, Employee>();

Parallel.ForEach(cx, _options, line =>
{
    Employee employee = null;
    switch (line.Substring(0, 2))
    {
        case EmployeeLine:
            // Employee is created and added to dictionary....
            _employeeDictionary.GetOrAdd(winID, employee);
            break;

        case DependentLine:
            // Dependent is created
            // WILL NOT BE ADDED IF THE EMPLOYEE HASN'T BEED ADDED YET
            if (_employeeDictionary.TryGetValue(dependent.EmpID, out employee))
            {
                lock (locker)
                {
                    employee.AddDependent(ci, dependent);
                }
            }
            break;
    }
});

4 个答案:

答案 0 :(得分:4)

  

我有没有办法在Parallel.ForEach

中等待/旋转/加入

是的,但是它们都会降低你的性能并引入死锁的可能性。所以这不是要走的路。

当然,最后if (Trygetvalue...))需要else分支,否则您将丢失数据。将受抚养人存储在列表中,以便在第一个parallel.ForEach()之后进行处理。

然后你不妨将所有家属存放在那里,而不是在第一次运行中查找它们。更简单,甚至可能更快。

答案 1 :(得分:2)

让并发工作而不是反对你的关键是让你的代码更加功能。而不是考虑命令式术语(&#34;为每一行做这个&#34;),考虑如何转换数据。并行进行数据转换:如果您不修改状态,那么您本身就是线程安全的。然后使用命令式编程仅用于真正需要的部分。

var cx = System.IO.File.ReadAllLines(path);

var lineInfo = cx.AsParallel()
    .Select(line => new {
        lineCode = line.Substring(0, 2),
        line
    })
    .ToList()
    .AsParallel();

var employeeDictionary = lineInfo
    .Where(e => e.lineCode == EmployeeLine)
    .Select(e => ParseEmployee(e.line))
    .ToDictionary(e => e.winId);

var dependentLookup = lineInfo
    .Where(e => e.lineCode == DependentLine)
    .Select(e => ParseDependent(e.line))
    .ToLookup(d => d.EmpId);

Parallel.ForEach(employeeDictionary.Values, _options, employee => 
{
    foreach(var dependent in dependentLookup[employee.winId])
    {
        // It's even better if you can have an "AddDependents" method
        // to avoid the foreach, and leverage the efficiencies of "AddRange"-type
        // methods.
        employee.AddDependent(dependent);
    }
});

值得注意的是,在此代码中进行并行处理实际上可能并不值得。我建议使用和不使用并行性对其进行基准测试,如果您没有看到显着的改进,请不要打扰。

答案 2 :(得分:1)

你拼出了自己的答案。他们是孤儿,因为员工还不存在。你必须等到最后才能确保员工在那里。锁定会降低性能。只需在初始文件读取完成后将孤立添加到该列表上的并发列表和并行foreach。

答案 3 :(得分:0)

假设您的线路数量超过您正在使用的机器上的处理器数量,您不会通过执行父集合然后执行子集合而失去显着的性能。这甚至可以避免检查这些值的开销,特别是因为你必须为这些选项锁定字典。

另一种选择是实现UnknownEmployee,它派生自Employee并表示已知家属的条目,但员工不是。父进程创建依赖项的任何时候都是未知的,创建一个UnknownEmployee条目并将其设置为依赖项。无论何时创建一个Employee,检查它们是否已存在,如果确实存在,请确保在添加到字典之前复制Dependents。