ToArray()可以抛出exception吗?

虽然这个问题的答案非常好,但它意味着你应该锁定对List.ToArray()的锁定以实现并发。 这篇博文还暗示它可能会灾难性地失败(但很少)。 在枚举列表或其他集合时,我通常使用ToArray而不是锁定,以避免“集合已修改,枚举可能无法完成”exception。 这个答案和博客文章都对这个假设提出质疑。

List.ToArray()的文档没有列出任何exception,所以我一直认为它总是会完成(虽然可能是陈旧的数据),虽然从数据一致性的角度来看它不是线程安全的,但它是线程从代码执行的角度来看是安全的 – 换句话说,它不会抛出exception并且调用它不会破坏底层集合的内部数据结构。

如果这个假设不正确,那么虽然它从未引起过问题,但它可能是高可用性应用程序中的定时炸弹。 什么是明确的答案?

由于一个简单的原因,您将找不到有关ToArray方法可能exception的文档。 这是一种具有许多“重载”的扩展方法。 它们都具有相同的方法签名,但实现对于不同的集合类型是不同的,例如ListHashSet

但是,对于大多数代码而言,我们可以安全地假设.NET框架BCL由于性能原因而不执行任何锁定。 我还非常具体地检查了ToList for List

 public T[] ToArray() { T[] array = new T[this._size]; Array.Copy(this._items, 0, array, 0, this._size); return array; } 

正如您可能想象的那样,它是非常简单的代码,最终在mscorlib执行。 对于此特定实现,您还可以查看在Array.Copy方法的MSDN页面中可能出现的exception。 它归结为一个exception,如果刚刚分配目标数组后列表的等级发生变化,则抛出该exception。

记住List是一个简单的例子,你可以想象exception的机会在需要更复杂的代码以便存储在数组中的结构上升。 Queue是一个更有可能失败的候选者:

 public T[] ToArray() { T[] array = new T[this._size]; if (this._size == 0) { return array; } if (this._head < this._tail) { Array.Copy(this._array, this._head, array, 0, this._size); } else { Array.Copy(this._array, this._head, array, 0, this._array.Length - this._head); Array.Copy(this._array, 0, array, this._array.Length - this._head, this._tail); } return array; } 

如果文档没有明确保证线程安全,或者原则上你无法承担它。 如果你认为它存在风险,那么你就有可能将一类漏洞投入到生产中,而这些漏洞是不可取的,并且有可能为你带来大量的生产力/可用性/资金。 你愿意冒这个险吗?

你永远不能测试一些线程安全的东西。 你永远不能确定。 您无法确定未来版本的行为方式是否相同。

以正确的方式做到并锁定。

顺便说一句,这些评论是针对List.ToArray ,这是ToArray更安全的版本之一。 我明白为什么人们会错误地认为它可以与列表的写入同时使用。 当然IEnumerable.ToArray 不可能是threadssafe,因为它是底层序列的属性。

ToArray不是线程安全的,这个代码certificate了这一点!

考虑一下这个相当荒谬的代码:

  List l = new List(); for (int i = 1; i < 100; i++) { l.Add(i); l.Add(i * 2); l.Add(i * i); } Thread th = new Thread(new ThreadStart(() => { int t=0; while (true) { //Thread.Sleep(200); switch (t) { case 0: l.Add(t); t = 1; break; case 1: l.RemoveAt(t); t = 0; break; } } })); th.Start(); try { while (true) { Array ai = l.ToArray(); //foreach (object o in ai) //{ // String str = o.ToString(); //} } } catch (System.Exception ex) { String str = ex.ToString(); } } 

由于l.Add(t)行,此代码将在非常短的时间内失败。 因为ToArray不是线程安全的,它会将数组分配给l的当前大小,然后我们将向l添加一个元素(在另一个线程中),然后它会尝试将当前大小l复制到ai并失败因为我有太多的元素。 ToArray抛出ArgumentException

看来你混淆了两件事:

  • 列表不支持在枚举时进行修改。 枚举列表时,枚举器检查每次迭代后列表是否已被修改。 在枚举列表之前调用List .ToArray解决了这个问题,因为您要枚举列表的快照,而不是列表本身。

  • List 不是线程安全的集合。 以上所有假设都来自同一个线程。 从两个线程访问列表总是需要锁定。 List .ToArray不是线程安全的,在这里没有帮助。

首先,您必须明确呼叫站点必须位于线程安全区域。 代码中的大多数区域都不是线程安全区域,并且在任何给定时间(对于大多数应用程序代码)将假定执行一个线程。 对于(非常粗略的估计)99%的应用程序代码,这个问题没有任何意义。

其次,你必须明确枚举函数的真正含义,因为这将根据你运行的枚举类型而有所不同 – 你是在谈论Enumerations的正常linq扩展吗?

第三,您提供给ToArray代码的链接及其周围的锁定语句充其量是无意义的:没有显示调用点也锁定在同一个集合上,它不能保证线程安全。

等等。