【问题标题】:Proper manipulation of IEnumerable<foo>正确操作 IEnumerable<foo>
【发布时间】:2015-02-18 07:04:59
【问题描述】:

关注我之前的question

在多线程程序中,不同的线程各自生成很长的结果列表。当线程完成它的任务时,我想将不同的列表连接到一个列表中。请注意以下几点:

public struct AccEntry
{
    internal AccEntry(int accumulation)
        : this()
    {
        Accumulation = accumulation;
    }
    public int Accumulation { private set; get; }
}


internal class Functions
{
     internal Functions(Object lockOnMe, IEnumerable<AccEntry> results)
     {
          _lockOnMe = lockOnMe;
          _results = results;
          _result = new List<AccEntry>();
     }

     private IEnumerable<AccEntry> _results { set; get; }
     private List<AccEntry> _result { set; get; }

     internal void SomeFunction()
     {
          /// some time consuming process that builds _result
          lock(_lockOnMe)
          {
              /// The problem is here! _results is always null.
              if (_results == null) _results = _result;
              else _results = _results.Concat(_result);
          }
     }
}

public class ParentClass
{
     public void DoJob()
     {
          IEnumerable<AccEntry> results = null;
          /// initialize and launch multiple threads where each 
          /// has a new instance of Functions, and call SomeFunction.
     }
}

正如代码中提到的,问题是_results 总是null。当线程更改将其设置为 _result 时,另一个线程再次发现它 null。我还尝试在 Functions 构造函数中为 results 使用 ref 关键字,但它没有改变任何东西。

假设以下代码按预期执行,我想知道我在上述代码中遗漏了什么?!!

List<int> listA = new List<int>();
List<int> listB = new List<int>();
listA.Add(10);
listB.Add(12);
IEnumerable<int> listC = null;
listC = listA;
listC = listC.Concat(listB);

【问题讨论】:

  • 您永远不会更新在方法 DoJob() 中定义的变量“results”,因为您通过 VALUE 将它传递给 Functions 的构造函数。一种可能的解决方案是将其初始化为 new List() 而不是 null。
  • @AugustoBarreto 我也尝试了 ref 关键字,但没有成功。 new List() 的初始化迫使我使用 AddRange 而不是 Concat,这在我的应用程序中被认为是高性能损失。跨度>

标签: c# multithreading locking concatenation ienumerable


【解决方案1】:

当您连接项目并将其分配回_results 变量时,这将替换您分配给变量的原始值。新的项目集合将是该实例本地的。

不要使用 IEnumerable&lt;&gt; 来更新它,而是使用 List&lt;&gt; 来添加项目:

internal class Functions
{
     internal Functions(Object lockOnMe, List<AccEntry> results)
     {
          _lockOnMe = lockOnMe;
          _results = results;
          _result = new List<AccEntry>();
     }

     private object _lockOnMe;
     private List<AccEntry> _results;
     private List<AccEntry> _result;

     internal void SomeFunction()
     {
          /// some time consuming process that builds _result
          lock(_lockOnMe)
          {
              _results.AddRange(_result);
          }
     }
}

只需确保在创建 Functions 实例之前创建列表即可。

【讨论】:

  • 由于性能损失,故意避免使用列表。 AddRange 需要大约 1 秒来连接两个列表,每个列表有 500 万个项目,其中 Concat 在几毫秒内完成相同的操作。您认为使用 Concat 代替 AddRange 可能有任何解决方法吗?
  • @Hamed:性能测试是比较苹果和橙子。 Concat 方法实际上并不连接集合,它创建一个查询,在迭代时连接集合。 Concat 调用非常快,因为它不执行实际工作,这将在您稍后迭代结果时完成。如果你真正意识到串联集合,你会发现AddRange 快了大约十倍。
  • 我才意识到Concat的意义。当您想要迭代时,它要求所有连接的引用都处于活动状态,这在我的场景中是不可接受的。
猜你喜欢
  • 1970-01-01
  • 2012-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多