List.ToArray()的文档没有列出任何异常,因此我一直认为它总是能完成(尽管可能带有过时数据),虽然从数据一致性的角度来看它不是线程安全的,但从代码执行的角度看,它是线程安全的--换句话说,它不会抛出异常,调用它也不会破坏基础集合的内部数据结构。
如果这个假设不正确,那么虽然它从来没有引起问题,在高可用性应用程序中它可能会成为一个定时炸弹。什么是确定性的答案?
public T[] ToArray()
{
T[] array = new T[this._size];
Array.Copy(this._items, 0, array, 0, this._size);
return array;
}
mscorlib
中执行。对于此特定实现,您还可以在 MSDN Array.Copy 方法页面 中查看可能发生的异常。问题归结为,如果目标数组分配后列表的级别发生更改,则会抛出异常。List<T>
是一个简单的例子,你可以想象需要更复杂的代码才能将其存储在数组中的结构出现异常的机会会增加。Queue<T>
的实现是更容易失败的候选者: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
不可能是线程安全的,因为这是基础序列的属性。
ToArray
,而且给人的印象是“无法通常实现线程安全的测试”。 - Sam HarwellToArray 不是线程安全的,这段代码可以证明!
考虑一下这个非常荒谬的代码:
List<int> l = new List<int>();
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
中,并因为l
含有太多元素而失败。 ToArray
会抛出一个ArgumentException
异常。
首先,您需要明确调用方必须位于线程安全区域。对于大多数应用程序代码来说,它们的大多数区域都不是线程安全的,并且会假定在任何给定时间只有一个执行线程(对于大多数应用程序代码)。对于大约99%的所有应用程序代码而言,这个问题并没有实际意义。
其次,您需要明确“枚举”函数到底是什么,因为这将取决于您正在遍历的枚举类型——您是否正在谈论 Enumerations 的普通 linq 扩展?
第三,您提供的 ToArray 代码链接以及周围的 lock 语句根本就是胡说八道:如果不显示调用同一集合上的锁定,它就不能保证线程安全。
等等。
看起来你混淆了两件事情:
List<T> 不支持在枚举时进行修改。当枚举列表时,枚举器会在每次迭代后检查列表是否已被修改。在枚举列表之前调用 List<T>.ToArray 可以解决这个问题,因为你在枚举列表的快照而不是列表本身。
List<T> 不是线程安全的集合。上述所有内容都假定从同一线程访问。从两个线程访问列表始终需要锁定。List<T>.ToArray 不是线程安全的,也无法在此处发挥作用。