List<T>方法调用线程同步问题

这是在调用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()内的操作没有加锁同步处理。

如下代码解说:

C# code
?
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()。
然后,在第一个先操作的,指令在时间片上,占第一位。

C# code
?
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的操作前,加个锁,同步一下。

例如:

正常代码:

C# code
?
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对象,所以我们要同步,不然会报异常。
}



异常代码:

C# code
?
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对象,所以我们要同步,不然会报异常。
}
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值