2

我有一个简单的while循环

IEnumerable<Foo> collection;
while (!bc.IsCompleted)
{
   collection = bc.Take();
}

bc 是一个BlockingCollection<IEnumerable<Foo>>。bc 包含 9 个 IEnumerable 集合和总共 260 万个 Foo 对象。循环在我的机器上运行大约需要 640 毫秒。一旦我在 while 循环中的 Take() 之后添加一个 foreach 循环,运行时间就会爆炸到 2400 毫秒。

foreach(Foo foo in collection)
{
}

在我单独设置的 List 或 Foo[] 或 IEnumerable 中迭代超过 260 万个元素大约需要 54 毫秒。

如果我只是添加一个集合转换而不是 foreach 循环,则会发生同样的情况,例如

List<Foo> fooList = collection.ToList();

或者

Foo[] fooArray = collection.ToArray();

它突然也需要超过 2000 毫秒才能执行。

怎么会这样?我完全没有解释或可能的原因。谁能指出我在这里缺少的东西?速度变慢不能由锁定/阻塞引起,因为我没有更改在比较之间访问 BlockingCollection 的方式。

感谢您的任何意见。

4

3 回答 3

1

你要加入什么样IEnumerable的队列?

请记住,LINQ 查询使用延迟执行;您的代码最终可能会评估消费者线程上的查询。ToList()在将元素放入队列之前尝试调用生产者线程。

于 2012-05-28T11:04:35.783 回答
1

一个IEnumerable可以代表一个延迟操作。有时(例如,使用 LINQ 或迭代器块)在迭代之前不会实际生成可枚举的内容。

因此,您IEnumerable<Foo>可能包含足够的信息来生成s,但实际上直到您在 a或 usingFoo中迭代可枚举时才会这样做。这就是为什么这些操作需要很长时间。foreachToList

于 2012-05-28T11:05:21.317 回答
1

与其他 LINQ 方法一样(我猜你正在使用 LINQ 的方法),此方法适用于延迟执行:

该方法是通过使用延迟执行来实现的。立即返回值是一个存储执行操作所需的所有信息的对象。直到通过直接调用其 GetEnumerator 方法或使用 Visual C# 中的 foreach 或 Visual Basic 中的 For Each 枚举对象后,才会执行此方法表示的查询

这意味着如果您不添加 foreach 循环的 ToList() 调用,则对 Take 的唯一调用不会真正产生任何结果,并且只有在使用迭代器(foreach/tolist)时才会产生实际结果,因此性能差异。

您与仅迭代列表的比较可能无法提供准确的结果;它不是对 List(foo) 的迭代需要时间,它可能是从您正在使用的阻塞集合中选择元素,这会减慢一切。
MSDN声称在 BlockingCollection 上使用常规 foreach(这可能是当您使用 LINQ 提供的 Take 时发生的情况,在这种情况下适用于 IEunmerable)使用底层集合的快照,这肯定会减慢对大型集合的处理.

于 2012-05-28T11:06:04.480 回答