克隆通用List <t> </t>时出现InvalidOperationException

时间:2013-11-20 12:56:49

标签: c# .net

我有一个非常讨厌的问题。

我想做的只是简单地迭代一般列表。允许在其他线程上修改此列表。

由于此并发调用,我的第一个实现抛出了InvalidOperationException。我了解到,在迭代此列表时,我不允许在列表中添加或删除项目。

好的,很久以前我开始在迭代集合的任何地方使用这样的代码:

foreach (CmdInterface cmd in new List<CmdInterface>(cmdList)) {
    ...
}

正如您所看到的,我不会迭代原始列表而是迭代。 这似乎工作了很长时间,但有时再次抛出InvalidOperationException,我无法弄清楚原因。它让我喝了很多咖啡,用了很长时间来寻找我做错了什么。

异常的堆栈跟踪是这样的:

    System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
   at System.Collections.Generic.List`1.Enumerator.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)

所以昨天我的想法是查看使用过的Construcor列表,了解Microsoft如何实现克隆。我发现了这个:

// Constructs a List, copying the contents of the given collection. The
    // size and capacity of the new list will both be equal to the size of the
    // given collection.
    //
    public List(IEnumerable<t> collection) {
        if (collection==null)
            ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);

        ICollection<t> c = collection as ICollection<t>;
        if( c != null) {
            int count = c.Count;
            _items = new T[count];
            c.CopyTo(_items, 0);
            _size = count;
        }
        else {
            _size = 0;
            _items = new T[_defaultCapacity];

            using(IEnumerator<t> en = collection.GetEnumerator()) {
                while(en.MoveNext()) {
                    Add(en.Current);
                }
            }
        }
    }

正如您所看到的,Microsoft本身正在迭代Collection以进行克隆。所以这可能是在某些罕见的情况下,更改列表可能会与克隆它相冲突。

所以我的问题是,您认为,这种行为是出于设计还是可能出现在某些问题上。 对我来说更重要的是,我可以使用CopyConstructor创建一个我的List的克隆来迭代线程安全。

感谢您的帮助 罗尼

1 个答案:

答案 0 :(得分:3)

您发现List<T>不是线程安全的。它只是在需要时迭代内容。

因此,您遇到的错误是设计上的。

至于为何如此实施;因为对资源的同步访问在资源方面是昂贵的。它不是常见实例中的必需行为,因此默认情况下不会实现。需要同步时的边缘情况将通过线程安全集合或开发人员自己的锁定机制来处理。

集合类型的变体提供了线程安全调用,但您可能还需要考虑为什么多个线程正在访问集合,以及自定义锁是否比阻塞集合更有用。

线程安全集合概述 - http://msdn.microsoft.com/en-us/library/dd997305%28v=vs.110%29.aspx

线程安全ConcurrentBag<T> - http://msdn.microsoft.com/en-us/library/dd381779%28v=vs.110%29.aspx