先晒晒报错时的日志:
[Exception] Source array was not long enough. Check srcIndex and length, and the array's lower bounds.
[Message] System.ArgumentException
[StackTrace] at System.Array.Copy(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length, Boolean reliable)
at System.Collections.Generic.Queue`1.SetCapacity(Int32 capacity)
at System.Collections.Generic.Queue`1.Enqueue(T item)
<some code other...>
看这个异常知道是Array.Copy的参数传错导致的崩溃。下面是我的用法:
//我自己的应用代码
lock (this.receiveQueueMutex)
{
this.receiveQueue.Enqueue(cmd);
}
//反汇编System.dll得到
public void Enqueue(T item)
{
if (this._size == this._array.Length)
{
int capacity = (int) ((this._array.Length * 200L) / 100L);
if (capacity < (this._array.Length + 4))
{
capacity = this._array.Length + 4;
}
this.SetCapacity(capacity);
}
this._array[this._tail] = item;
this._tail = (this._tail + 1) % this._array.Length;
this._size++;
this._version++;
}
//反汇编System.dll得到
private void SetCapacity(int capacity)
{
T[] destinationArray = new T[capacity];
if (this._size > 0)
{
if (this._head < this._tail)
{
Array.Copy(this._array, this._head, destinationArray, 0, this._size);
}
else
{
Array.Copy(this._array, this._head, destinationArray, 0, this._array.Length - this._head);
Array.Copy(this._array, 0, destinationArray, this._array.Length - this._head, this._tail);
}
}
this._array = destinationArray;
this._head = 0;
this._tail = this._size;
this._version++;
}
看了这些代码之后也没有得到什么有效的结论。所以我也在怀疑是不是framework有bug了。而且还整了好几个测试用例去疯狂的向Queue<T>中Enqueue操作,但能得到的唯一的异常就是Out Of Memory. Shift! 为什么这个错误就不能复现呢?
其实对于很难复现的问题最先要想到的就是指针,这肯定不可能了。其次就是多线程问题,可惜我当时没有想到这点,也许是被外面的那个lock给忽悠了。刚才在网上搜到了一篇帖子http://odetocode.com/Blogs/scott/archive/2007/01/09/9746.aspx,其中谈到对非线程安全的Queue<T>的使用时应该注意lock,否则会发生线程访问的冲突问题,结果报出来的错就会这样的风马驴不相及。这段解释很到位,老外回帖很认真的:
The queue was calling Array.Copy to "expand" into a bigger array and allow more items to fit inside. At the same time, another thread changed the queue by trying to insert a string, and the queue's internal state became corrupt.
A lock statement inside PopulateQueue would fix the problem by only permitting a single thread at a time to modify the queue.
MSDN中也明确的谈到了Queue<T>的线程安全问题:
此类型的公共静态(在 Visual Basic 中为 Shared)成员是线程安全的。但不能保证任何实例成员是线程安全的。
只要不修改该集合,Queue 就可以同时支持多个阅读器。即便如此,从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。若要确保枚举过程中的线程安全,可以在整个枚举过程中锁定集合。若要允许多个线程访问集合以进行读写操作,则必须实现自己的同步。
仔细搜了一下自己的代码,的确发现有一处Clear的操作没有加lock。呜呼,这种小概率事件也能给我遇上。
忠告自己:多线程中的容器一定要记得锁,而且锁的不全也不行!不能留下任何后门,后门多了这规矩也就乱了。
评论