这是在调用List<T>的AddRange方法时,遇到“System.ArgumentException: 目标数组的长度不够。请检查 destIndex 和长度以及数组的下限。”的问题。
初步估计是线程安全的问题。
在搜索解决方法时看到的linjf520的一篇分析,原文地址如下:
http://bbs.csdn.net/topics/370224912
觉得它对这个问题的分析相对独特,故转过来了。
System.ArgumentException: 目标数组的长度不够。请检查 destIndex 和长度以及数组的下限。
在 System.Array.Copy(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length, Boolean reliable)
在 System.Array.Copy(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length)
在 System.Collections.Generic.List`1.ToArray()
这是怎么回事?
这里说说怎么避免这问题出现。
因为之前没有大量的对List<T>的测试,也不知道,List<T>是否线程安全的。当然线程安全的,
总得要有同步操作。这里肯定就会耗性能了。
List<T>.ToArray()出异常了? 我想:出于性能考虑,.net 的List<T>.ToArray()内的操作没有加锁同步处理。
如下代码解说:
1
2
3
4
5
6
|
public
T[] ToArray()
{
T[] destinationArray =
new
T[
this
._size];
Array.Copy(
this
._items, 0, destinationArray, 0,
this
._size);
return
destinationArray;
}
|
以上代码是用.net refelctor 反编译的。List<T>.ToArray()方法内的代码。
果然是没有加lock或是其它的同步操作。
原因:有两操作A,B,分别异步的操作了一个.Add(T item)或是.Remove(T item)方法别一个List<T>的.ToArray()。
然后,在第一个先操作的,指令在时间片上,占第一位。
1
2
3
4
5
6
7
8
9
|
public
void
Add(T item)
{
if
(
this
._size ==
this
._items.Length)
{
this
.EnsureCapacity(
this
._size + 1);
}
this
._items[
this
._size++] = item;
this
._version++;
}
|
从上面来看,Add(T item)也是没有加同步锁的
if (this._size == this._items.Length)//A的
假设:
紧跟着下一条执行的指令应该是:
T[] destinationArray = new T[this._size];//B的
但,由于异步操作。
然后,在第二个操作的时候,因为是异步的,所以有可能在第一个操作的指令
紧跟着要执行的指定就成了第二个操作的第一个指令,依此类推下去,将所有执行顺序的列出来
//假设:执行前,this.Count == 10
if (this._size == this._items.Length)//A的
this.EnsureCapacity(this._size + 1);//A的
T[] destinationArray = new T[this._size];//B的 假设计this.Count = 10
this._items[this._size++] = item;//A的,这时,this.Count == 11了
Array.Copy(this._items, 0, destinationArray, 0, this._size);
//B的,这时,所有this.Count == 1,而刚定义的destinationArray.Length == 10的,
//所以这时,一旦执行COPY就会出下限不足的异常。
所以,现在我们知道了List不是线程安全的,很多时候,我们都必须自己要对List的操作前,加个锁,同步一下。
例如:
正常代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class
TestClass{}
static
List<TestClass> testClassList =
new
List<TestClass>();
static
void
main()
{
//这里用线程来摸拟CPU异步吧。
Thread[] threadArray =
new
Thread[100];
for
(
int
i=0;i<100;i++)
{
threadArray[i]=
new
Thread(ThreadMethod);
threadArray[i].IsBackGround=
true
;
threadArray[i].Start();
}
}
void
ThreadMethod()
{
testClassList.Add(
new
TestClass());
TestClass[] testClassArray = testClassList.ToArray();
//这里由于线程异步操作同一个testClassList对象,所以我们要同步,不然会报异常。
}
|
异常代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class
TestClass{}
static
List<TestClass> testClassList =
new
List<TestClass>();
static
void
main()
{
//这里用线程来摸拟CPU异步吧。
Thread[] threadArray =
new
Thread[100];
for
(
int
i=0;i<100;i++)
{
threadArray[i]=
new
Thread(ThreadMethod);
threadArray[i].IsBackGround=
true
;
threadArray[i].Start();
}
}
void
ThreadMethod()
{
lock
(testClassList)
//这里加个锁,同步一下。对Add与ToArray的时候,保证内有一个线程操作正常指令顺序。
{
testClassList.Add(
new
TestClass());
TestClass[] testClassArray = testClassList.ToArray();
//这里由于线程异步操作同一个testClassList对象,所以我们要同步,不然会报异常。
}
}
|